Adding ATAPI support to the new ATA bus_manager. Only tested in QEMU.

git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30080 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Michael Lotz
2009-04-09 19:53:26 +00:00
parent 9ec59fc934
commit 22805aae7f
4 changed files with 311 additions and 71 deletions
@@ -14,7 +14,7 @@ ATAChannel::ATAChannel(device_node *node)
fChannelID(0),
fController(NULL),
fCookie(NULL),
fExpectsDMATransfer(false),
fExpectsInterrupt(false),
fStatus(B_NO_INIT),
fSCSIBus(NULL),
fDeviceCount(0),
@@ -23,8 +23,8 @@ ATAChannel::ATAChannel(device_node *node)
fRequest(NULL)
{
mutex_init(&fExecutionLock, "ata io execution");
B_INITIALIZE_SPINLOCK(&fDMATransferLock);
fDMATransferCondition.Init(this, "ata dma transfer");
B_INITIALIZE_SPINLOCK(&fInterruptLock);
fInterruptCondition.Init(this, "ata dma transfer");
gDeviceManager->get_attr_uint32(node, ATA_CHANNEL_ID_ITEM, &fChannelID,
true);
@@ -234,6 +234,9 @@ ATAChannel::ExecuteIO(scsi_ccb *ccb)
return B_OK;
}
// we aren't a check sense request, clear sense data for new request
fRequest->ClearSense();
if (ccb->target_id >= fDeviceCount) {
TRACE_ERROR("invalid target device\n");
fRequest->SetStatus(SCSI_SEL_TIMEOUT);
@@ -393,7 +396,7 @@ ATAChannel::Wait(uint8 setBits, uint8 clearedBits, uint32 flags,
_FlushAndWait(1);
while (true) {
uint8 status = _AltStatus();
uint8 status = AltStatus();
if ((flags & ATA_CHECK_ERROR_BIT) != 0
&& (status & ATA_STATUS_ERROR) != 0)
return B_ERROR;
@@ -443,7 +446,37 @@ ATAChannel::WaitDeviceReady()
status_t
ATAChannel::WaitForIdle()
{
return Wait(0, ATA_STATUS_BUSY | ATA_STATUS_DATA_REQUEST, 0, 20 * 1000);
return Wait(0, ATA_STATUS_BUSY | ATA_STATUS_DATA_REQUEST, 0, 50 * 1000);
}
void
ATAChannel::PrepareWaitingForInterrupt()
{
TRACE_FUNCTION("\n");
InterruptsSpinLocker locker(fInterruptLock);
fExpectsInterrupt = true;
fInterruptCondition.Add(&fInterruptConditionEntry);
}
status_t
ATAChannel::WaitForInterrupt(bigtime_t timeout)
{
TRACE_FUNCTION("timeout: %lld\n", timeout);
status_t result = fInterruptConditionEntry.Wait(B_RELATIVE_TIMEOUT,
timeout);
InterruptsSpinLocker locker(fInterruptLock);
fExpectsInterrupt = false;
locker.Unlock();
if (result != B_OK) {
TRACE_ERROR("timeout waiting for interrupt\n");
return B_TIMED_OUT;
}
return B_OK;
}
@@ -464,7 +497,7 @@ ATAChannel::SendRequest(ATARequest *request, uint32 flags)
}
if ((flags & ATA_DEVICE_READY_REQUIRED) != 0
&& (_AltStatus() & ATA_STATUS_DEVICE_READY) == 0) {
&& (AltStatus() & ATA_STATUS_DEVICE_READY) == 0) {
TRACE_ERROR("device ready not set\n");
request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_ERROR;
@@ -583,35 +616,16 @@ ATAChannel::PrepareDMA(ATARequest *request)
status_t
ATAChannel::FinishDMA()
ATAChannel::StartDMA()
{
return fController->finish_dma(fCookie);
return fController->start_dma(fCookie);
}
status_t
ATAChannel::ExecuteDMATransfer(ATARequest *request)
ATAChannel::FinishDMA()
{
InterruptsSpinLocker locker(fDMATransferLock);
fExpectsDMATransfer = true;
ConditionVariableEntry entry;
fDMATransferCondition.Add(&entry);
locker.Unlock();
fController->start_dma(fCookie);
bigtime_t timeout = system_time() + request->Timeout();
status_t waitResult = entry.Wait(B_ABSOLUTE_TIMEOUT, timeout);
locker.Lock();
fExpectsDMATransfer = false;
locker.Unlock();
if (waitResult != B_OK) {
TRACE_ERROR("timeout waiting for DMA transfer\n");
return B_TIMED_OUT;
}
return B_OK;
return fController->finish_dma(fCookie);
}
@@ -646,7 +660,7 @@ ATAChannel::ExecutePIOTransfer(ATARequest *request)
// wait 1 pio cycle
if (*blocksLeft > 0)
_AltStatus();
AltStatus();
}
if (result == B_OK && WaitDataRequest(false) != B_OK) {
@@ -658,6 +672,20 @@ ATAChannel::ExecutePIOTransfer(ATARequest *request)
}
status_t
ATAChannel::ReadRegs(ATADevice *device)
{
return _ReadRegs(device->TaskFile(), device->RegisterMask());
}
uint8
ATAChannel::AltStatus()
{
return fController->get_altstatus(fCookie);
}
status_t
ATAChannel::ReadPIO(uint8 *buffer, size_t length)
{
@@ -666,11 +694,19 @@ ATAChannel::ReadPIO(uint8 *buffer, size_t length)
}
status_t
ATAChannel::WritePIO(uint8 *buffer, size_t length)
{
return fController->write_pio(fCookie, (uint16 *)buffer,
length / sizeof(uint16), true);
}
void
ATAChannel::Interrupt(uint8 status)
{
SpinLocker locker(fDMATransferLock);
if (!fExpectsDMATransfer) {
SpinLocker locker(fInterruptLock);
if (!fExpectsInterrupt) {
TRACE_ERROR("interrupt when not expecting transfer\n");
return;
}
@@ -680,7 +716,7 @@ ATAChannel::Interrupt(uint8 status)
return;
}
fDMATransferCondition.NotifyAll();
fInterruptCondition.NotifyAll();
}
@@ -706,17 +742,10 @@ ATAChannel::_WriteControl(uint8 value)
}
uint8
ATAChannel::_AltStatus()
{
return fController->get_altstatus(fCookie);
}
void
ATAChannel::_FlushAndWait(bigtime_t waitTime)
{
_AltStatus();
AltStatus();
if (waitTime > 100)
snooze(waitTime);
else
@@ -749,7 +778,7 @@ ATAChannel::_ReadPIOBlock(ATARequest *request, size_t length)
if (transferred >= length)
return B_OK;
TRACE_ERROR("pio read: discarding after %lu bytes", transferred);
TRACE_ERROR("pio read: discarding after %lu bytes\n", transferred);
uint8 buffer[32];
length -= transferred;
@@ -799,7 +828,7 @@ ATAChannel::_WritePIOBlock(ATARequest *request, size_t length)
// only solution is to send zero bytes, though it's BAD
static const uint8 buffer[32] = {};
TRACE_ERROR("pio write: discarding after %lu bytes", transferred);
TRACE_ERROR("pio write: discarding after %lu bytes\n", transferred);
length -= transferred;
while (length > 0) {
@@ -12,14 +12,14 @@
ATADevice::ATADevice(ATAChannel *channel, uint8 index)
: fChannel(channel),
fIndex(index),
fUseLBA(false),
fUse48Bits(false),
fRegisterMask(0),
fUseDMA(channel->UseDMA()),
fDMAMode(0),
fDMAFailures(0),
fTotalSectors(0),
fRegisterMask(0)
fIndex(index),
fUseLBA(false),
fUse48Bits(false),
fTotalSectors(0)
{
memset(&fInfoBlock, 0, sizeof(fInfoBlock));
memset(&fTaskFile, 0, sizeof(fTaskFile));
@@ -202,7 +202,6 @@ ATADevice::ExecuteIO(ATARequest *request)
TRACE("request: 0x%02x\n", ccb->cdb[0]);
request->ClearSense();
switch (ccb->cdb[0]) {
case SCSI_OP_TEST_UNIT_READY:
return TestUnitReady(request);
@@ -541,7 +540,10 @@ ATADevice::ExecuteReadWrite(ATARequest *request, uint64 address,
}
if (request->UseDMA()) {
result = fChannel->ExecuteDMATransfer(request);
fChannel->PrepareWaitingForInterrupt();
fChannel->StartDMA();
result = fChannel->WaitForInterrupt(request->Timeout());
status_t dmaResult = fChannel->FinishDMA();
if (result == B_OK && dmaResult == B_OK) {
fDMAFailures = 0;
@@ -10,8 +10,7 @@
#include "ATAPrivate.h"
ATAPIDevice::ATAPIDevice(ATAChannel *channel, uint8 index)
: ATADevice(channel, index),
fLastLun(1)
: ATADevice(channel, index)
{
}
@@ -21,10 +20,204 @@ ATAPIDevice::~ATAPIDevice()
}
status_t
ATAPIDevice::SendPacket(ATARequest *request)
{
TRACE_FUNCTION("%p\n", request);
// only READ/WRITE commands can use DMA
// (the device may support it always, but IDE controllers don't
// report how much data is transmitted, and this information is
// crucial for the SCSI protocol)
// special offer: let READ_CD commands use DMA too
uint8 command = fPacket[0];
request->SetUseDMA(UseDMA()
&& (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;
}
if (fInterruptsForPacket)
fChannel->PrepareWaitingForInterrupt();
status_t result = fChannel->SendRequest(request, ATA_DMA_TRANSFER);
if (result != B_OK) {
TRACE_ERROR("failed to send packet request\n");
if (request->UseDMA())
fChannel->FinishDMA();
return result;
}
// wait for device to get ready for packet transmission
bool timedOut = false;
if (fInterruptsForPacket)
timedOut = fChannel->WaitForInterrupt(request->Timeout()) != B_OK;
else {
timedOut = fChannel->Wait(ATA_STATUS_DATA_REQUEST, ATA_STATUS_BUSY, 0,
100 * 1000) != B_OK;
}
if (timedOut) {
TRACE_ERROR("timeout waiting for data request\n");
if (request->UseDMA())
fChannel->FinishDMA();
request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_TIMED_OUT;
}
// make sure device really asks for command packet
fRegisterMask = ATA_MASK_IREASON;
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;
}
// some old drives need a delay before submitting the packet
spin(10);
fChannel->PrepareWaitingForInterrupt();
// write packet
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->UseDMA()) {
fChannel->StartDMA();
result = fChannel->WaitForInterrupt(request->Timeout());
status_t dmaResult = fChannel->FinishDMA();
if (result == B_OK && dmaResult == B_OK) {
fDMAFailures = 0;
request->CCB()->data_resid = 0;
} else {
if (dmaResult != B_OK) {
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;
}
} else {
// timeout
request->SetStatus(SCSI_CMD_TIMEOUT);
}
}
} else {
result = fChannel->WaitForInterrupt(request->Timeout());
if (result != B_OK) {
TRACE_ERROR("timeout waiting for device to request data\n");
request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_TIMED_OUT;
}
while (true) {
uint8 altStatus = fChannel->AltStatus();
if ((altStatus & ATA_STATUS_DATA_REQUEST) == 0)
break;
fRegisterMask = ATA_MASK_ERROR | ATA_MASK_IREASON;
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;
}
fRegisterMask = ATA_MASK_BYTE_COUNT;
fChannel->ReadRegs(this);
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->SetBlocksLeft((length + 511) / 512);
if (fChannel->ExecutePIOTransfer(request) != B_OK) {
TRACE_ERROR("failed to transfer data\n");
request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_ERROR;
}
}
}
return fChannel->FinishRequest(request, ATA_WAIT_FINISH, ATA_ERROR_ABORTED);
}
status_t
ATAPIDevice::ExecuteIO(ATARequest *request)
{
request->SetStatus(SCSI_SEL_TIMEOUT);
request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_CDB_FIELD);
return B_ERROR;
scsi_ccb *ccb = request->CCB();
if (ccb->target_lun > fInfoBlock.last_lun) {
TRACE_ERROR("invalid target lun %d, last lun is %d\n", ccb->target_lun,
fInfoBlock.last_lun);
request->SetStatus(SCSI_SEL_TIMEOUT);
return B_BAD_INDEX;
}
// ATAPI command packets are 12 bytes long;
// if the command is shorter, remaining bytes must be padded with zeros
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._0.atapi.ATAPI != 2)
return B_ERROR;
fTaskFile.packet.lun = 0;
fInterruptsForPacket = fInfoBlock._0.atapi.drq_speed == 1;
status_t result = ConfigureDMA();
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();
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;
}
@@ -85,6 +85,9 @@ public:
status_t WaitDeviceReady();
status_t WaitForIdle();
void PrepareWaitingForInterrupt();
status_t WaitForInterrupt(bigtime_t timeout);
// request handling
status_t SendRequest(ATARequest *request,
uint32 flags);
@@ -93,12 +96,16 @@ public:
// data transfers
status_t PrepareDMA(ATARequest *request);
status_t StartDMA();
status_t FinishDMA();
status_t ExecuteDMATransfer(ATARequest *request);
status_t ExecutePIOTransfer(ATARequest *request);
status_t ReadRegs(ATADevice *device);
uint8 AltStatus();
status_t ReadPIO(uint8 *buffer, size_t length);
status_t WritePIO(uint8 *buffer, size_t length);
void Interrupt(uint8 status);
@@ -109,7 +116,6 @@ private:
ata_reg_mask mask);
status_t _WriteControl(uint8 value);
uint8 _AltStatus();
void _FlushAndWait(bigtime_t waitTime);
status_t _ReadPIOBlock(ATARequest *request,
@@ -133,9 +139,10 @@ private:
void * fCookie;
mutex fExecutionLock;
spinlock fDMATransferLock;
ConditionVariable fDMATransferCondition;
bool fExpectsDMATransfer;
spinlock fInterruptLock;
ConditionVariable fInterruptCondition;
ConditionVariableEntry fInterruptConditionEntry;
bool fExpectsInterrupt;
status_t fStatus;
scsi_bus fSCSIBus;
@@ -179,29 +186,32 @@ virtual bool IsATAPI() { return false; };
status_t DisableCommandQueueing();
status_t ConfigureDMA();
status_t Configure();
virtual status_t Configure();
status_t Identify();
status_t ExecuteReadWrite(ATARequest *request,
uint64 address, uint32 sectorCount);
protected:
const char * _DebugContext() { return fDebugContext; };
ATAChannel * fChannel;
ata_device_infoblock fInfoBlock;
ata_task_file fTaskFile;
ata_reg_mask fRegisterMask;
bool fUseDMA;
uint8 fDMAMode;
uint8 fDMAFailures;
private:
status_t _FillTaskFile(ATARequest *request,
uint64 address);
const char * _DebugContext() { return fDebugContext; };
ATAChannel * fChannel;
uint8 fIndex;
bool fUseLBA;
bool fUse48Bits;
bool fUseDMA;
uint8 fDMAMode;
uint8 fDMAFailures;
uint64 fTotalSectors;
ata_device_infoblock fInfoBlock;
ata_task_file fTaskFile;
ata_reg_mask fRegisterMask;
char fDebugContext[16];
};
@@ -213,12 +223,18 @@ public:
uint8 index);
virtual ~ATAPIDevice();
status_t SendPacket(ATARequest *request);
virtual status_t ExecuteIO(ATARequest *request);
virtual bool IsATAPI() { return true; };
virtual status_t Configure();
private:
uint8 fLastLun;
status_t _FillTaskFilePacket(ATARequest *request);
bool fInterruptsForPacket;
uint8 fPacket[12];
};