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), fChannelID(0),
fController(NULL), fController(NULL),
fCookie(NULL), fCookie(NULL),
fExpectsDMATransfer(false), fExpectsInterrupt(false),
fStatus(B_NO_INIT), fStatus(B_NO_INIT),
fSCSIBus(NULL), fSCSIBus(NULL),
fDeviceCount(0), fDeviceCount(0),
@@ -23,8 +23,8 @@ ATAChannel::ATAChannel(device_node *node)
fRequest(NULL) fRequest(NULL)
{ {
mutex_init(&fExecutionLock, "ata io execution"); mutex_init(&fExecutionLock, "ata io execution");
B_INITIALIZE_SPINLOCK(&fDMATransferLock); B_INITIALIZE_SPINLOCK(&fInterruptLock);
fDMATransferCondition.Init(this, "ata dma transfer"); fInterruptCondition.Init(this, "ata dma transfer");
gDeviceManager->get_attr_uint32(node, ATA_CHANNEL_ID_ITEM, &fChannelID, gDeviceManager->get_attr_uint32(node, ATA_CHANNEL_ID_ITEM, &fChannelID,
true); true);
@@ -234,6 +234,9 @@ ATAChannel::ExecuteIO(scsi_ccb *ccb)
return B_OK; return B_OK;
} }
// we aren't a check sense request, clear sense data for new request
fRequest->ClearSense();
if (ccb->target_id >= fDeviceCount) { if (ccb->target_id >= fDeviceCount) {
TRACE_ERROR("invalid target device\n"); TRACE_ERROR("invalid target device\n");
fRequest->SetStatus(SCSI_SEL_TIMEOUT); fRequest->SetStatus(SCSI_SEL_TIMEOUT);
@@ -393,7 +396,7 @@ ATAChannel::Wait(uint8 setBits, uint8 clearedBits, uint32 flags,
_FlushAndWait(1); _FlushAndWait(1);
while (true) { while (true) {
uint8 status = _AltStatus(); uint8 status = AltStatus();
if ((flags & ATA_CHECK_ERROR_BIT) != 0 if ((flags & ATA_CHECK_ERROR_BIT) != 0
&& (status & ATA_STATUS_ERROR) != 0) && (status & ATA_STATUS_ERROR) != 0)
return B_ERROR; return B_ERROR;
@@ -443,7 +446,37 @@ ATAChannel::WaitDeviceReady()
status_t status_t
ATAChannel::WaitForIdle() 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 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"); TRACE_ERROR("device ready not set\n");
request->SetStatus(SCSI_SEQUENCE_FAIL); request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_ERROR; return B_ERROR;
@@ -583,35 +616,16 @@ ATAChannel::PrepareDMA(ATARequest *request)
status_t status_t
ATAChannel::FinishDMA() ATAChannel::StartDMA()
{ {
return fController->finish_dma(fCookie); return fController->start_dma(fCookie);
} }
status_t status_t
ATAChannel::ExecuteDMATransfer(ATARequest *request) ATAChannel::FinishDMA()
{ {
InterruptsSpinLocker locker(fDMATransferLock); return fController->finish_dma(fCookie);
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;
} }
@@ -646,7 +660,7 @@ ATAChannel::ExecutePIOTransfer(ATARequest *request)
// wait 1 pio cycle // wait 1 pio cycle
if (*blocksLeft > 0) if (*blocksLeft > 0)
_AltStatus(); AltStatus();
} }
if (result == B_OK && WaitDataRequest(false) != B_OK) { 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 status_t
ATAChannel::ReadPIO(uint8 *buffer, size_t length) 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 void
ATAChannel::Interrupt(uint8 status) ATAChannel::Interrupt(uint8 status)
{ {
SpinLocker locker(fDMATransferLock); SpinLocker locker(fInterruptLock);
if (!fExpectsDMATransfer) { if (!fExpectsInterrupt) {
TRACE_ERROR("interrupt when not expecting transfer\n"); TRACE_ERROR("interrupt when not expecting transfer\n");
return; return;
} }
@@ -680,7 +716,7 @@ ATAChannel::Interrupt(uint8 status)
return; return;
} }
fDMATransferCondition.NotifyAll(); fInterruptCondition.NotifyAll();
} }
@@ -706,17 +742,10 @@ ATAChannel::_WriteControl(uint8 value)
} }
uint8
ATAChannel::_AltStatus()
{
return fController->get_altstatus(fCookie);
}
void void
ATAChannel::_FlushAndWait(bigtime_t waitTime) ATAChannel::_FlushAndWait(bigtime_t waitTime)
{ {
_AltStatus(); AltStatus();
if (waitTime > 100) if (waitTime > 100)
snooze(waitTime); snooze(waitTime);
else else
@@ -749,7 +778,7 @@ ATAChannel::_ReadPIOBlock(ATARequest *request, size_t length)
if (transferred >= length) if (transferred >= length)
return B_OK; 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]; uint8 buffer[32];
length -= transferred; length -= transferred;
@@ -799,7 +828,7 @@ ATAChannel::_WritePIOBlock(ATARequest *request, size_t length)
// only solution is to send zero bytes, though it's BAD // only solution is to send zero bytes, though it's BAD
static const uint8 buffer[32] = {}; 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; length -= transferred;
while (length > 0) { while (length > 0) {
@@ -12,14 +12,14 @@
ATADevice::ATADevice(ATAChannel *channel, uint8 index) ATADevice::ATADevice(ATAChannel *channel, uint8 index)
: fChannel(channel), : fChannel(channel),
fIndex(index), fRegisterMask(0),
fUseLBA(false),
fUse48Bits(false),
fUseDMA(channel->UseDMA()), fUseDMA(channel->UseDMA()),
fDMAMode(0), fDMAMode(0),
fDMAFailures(0), fDMAFailures(0),
fTotalSectors(0), fIndex(index),
fRegisterMask(0) fUseLBA(false),
fUse48Bits(false),
fTotalSectors(0)
{ {
memset(&fInfoBlock, 0, sizeof(fInfoBlock)); memset(&fInfoBlock, 0, sizeof(fInfoBlock));
memset(&fTaskFile, 0, sizeof(fTaskFile)); memset(&fTaskFile, 0, sizeof(fTaskFile));
@@ -202,7 +202,6 @@ ATADevice::ExecuteIO(ATARequest *request)
TRACE("request: 0x%02x\n", ccb->cdb[0]); TRACE("request: 0x%02x\n", ccb->cdb[0]);
request->ClearSense();
switch (ccb->cdb[0]) { switch (ccb->cdb[0]) {
case SCSI_OP_TEST_UNIT_READY: case SCSI_OP_TEST_UNIT_READY:
return TestUnitReady(request); return TestUnitReady(request);
@@ -541,7 +540,10 @@ ATADevice::ExecuteReadWrite(ATARequest *request, uint64 address,
} }
if (request->UseDMA()) { if (request->UseDMA()) {
result = fChannel->ExecuteDMATransfer(request); fChannel->PrepareWaitingForInterrupt();
fChannel->StartDMA();
result = fChannel->WaitForInterrupt(request->Timeout());
status_t dmaResult = fChannel->FinishDMA(); status_t dmaResult = fChannel->FinishDMA();
if (result == B_OK && dmaResult == B_OK) { if (result == B_OK && dmaResult == B_OK) {
fDMAFailures = 0; fDMAFailures = 0;
@@ -10,8 +10,7 @@
#include "ATAPrivate.h" #include "ATAPrivate.h"
ATAPIDevice::ATAPIDevice(ATAChannel *channel, uint8 index) ATAPIDevice::ATAPIDevice(ATAChannel *channel, uint8 index)
: ATADevice(channel, index), : ATADevice(channel, index)
fLastLun(1)
{ {
} }
@@ -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 status_t
ATAPIDevice::ExecuteIO(ATARequest *request) ATAPIDevice::ExecuteIO(ATARequest *request)
{ {
request->SetStatus(SCSI_SEL_TIMEOUT); scsi_ccb *ccb = request->CCB();
request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_CDB_FIELD); if (ccb->target_lun > fInfoBlock.last_lun) {
return B_ERROR; 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 WaitDeviceReady();
status_t WaitForIdle(); status_t WaitForIdle();
void PrepareWaitingForInterrupt();
status_t WaitForInterrupt(bigtime_t timeout);
// request handling // request handling
status_t SendRequest(ATARequest *request, status_t SendRequest(ATARequest *request,
uint32 flags); uint32 flags);
@@ -93,12 +96,16 @@ public:
// data transfers // data transfers
status_t PrepareDMA(ATARequest *request); status_t PrepareDMA(ATARequest *request);
status_t StartDMA();
status_t FinishDMA(); status_t FinishDMA();
status_t ExecuteDMATransfer(ATARequest *request);
status_t ExecutePIOTransfer(ATARequest *request); status_t ExecutePIOTransfer(ATARequest *request);
status_t ReadRegs(ATADevice *device);
uint8 AltStatus();
status_t ReadPIO(uint8 *buffer, size_t length); status_t ReadPIO(uint8 *buffer, size_t length);
status_t WritePIO(uint8 *buffer, size_t length);
void Interrupt(uint8 status); void Interrupt(uint8 status);
@@ -109,7 +116,6 @@ private:
ata_reg_mask mask); ata_reg_mask mask);
status_t _WriteControl(uint8 value); status_t _WriteControl(uint8 value);
uint8 _AltStatus();
void _FlushAndWait(bigtime_t waitTime); void _FlushAndWait(bigtime_t waitTime);
status_t _ReadPIOBlock(ATARequest *request, status_t _ReadPIOBlock(ATARequest *request,
@@ -133,9 +139,10 @@ private:
void * fCookie; void * fCookie;
mutex fExecutionLock; mutex fExecutionLock;
spinlock fDMATransferLock; spinlock fInterruptLock;
ConditionVariable fDMATransferCondition; ConditionVariable fInterruptCondition;
bool fExpectsDMATransfer; ConditionVariableEntry fInterruptConditionEntry;
bool fExpectsInterrupt;
status_t fStatus; status_t fStatus;
scsi_bus fSCSIBus; scsi_bus fSCSIBus;
@@ -179,29 +186,32 @@ virtual bool IsATAPI() { return false; };
status_t DisableCommandQueueing(); status_t DisableCommandQueueing();
status_t ConfigureDMA(); status_t ConfigureDMA();
status_t Configure(); virtual status_t Configure();
status_t Identify(); status_t Identify();
status_t ExecuteReadWrite(ATARequest *request, status_t ExecuteReadWrite(ATARequest *request,
uint64 address, uint32 sectorCount); 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: private:
status_t _FillTaskFile(ATARequest *request, status_t _FillTaskFile(ATARequest *request,
uint64 address); uint64 address);
const char * _DebugContext() { return fDebugContext; };
ATAChannel * fChannel;
uint8 fIndex; uint8 fIndex;
bool fUseLBA; bool fUseLBA;
bool fUse48Bits; bool fUse48Bits;
bool fUseDMA;
uint8 fDMAMode;
uint8 fDMAFailures;
uint64 fTotalSectors; uint64 fTotalSectors;
ata_device_infoblock fInfoBlock;
ata_task_file fTaskFile;
ata_reg_mask fRegisterMask;
char fDebugContext[16]; char fDebugContext[16];
}; };
@@ -213,12 +223,18 @@ public:
uint8 index); uint8 index);
virtual ~ATAPIDevice(); virtual ~ATAPIDevice();
status_t SendPacket(ATARequest *request);
virtual status_t ExecuteIO(ATARequest *request); virtual status_t ExecuteIO(ATARequest *request);
virtual bool IsATAPI() { return true; }; virtual bool IsATAPI() { return true; };
virtual status_t Configure();
private: private:
uint8 fLastLun; status_t _FillTaskFilePacket(ATARequest *request);
bool fInterruptsForPacket;
uint8 fPacket[12];
}; };