diff --git a/src/add-ons/kernel/bus_managers/ata/ATAChannel.cpp b/src/add-ons/kernel/bus_managers/ata/ATAChannel.cpp index 71e9145ec6..e5e6fc1cff 100644 --- a/src/add-ons/kernel/bus_managers/ata/ATAChannel.cpp +++ b/src/add-ons/kernel/bus_managers/ata/ATAChannel.cpp @@ -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) { diff --git a/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp b/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp index ff780c7f06..1cb3f8b5a7 100644 --- a/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp +++ b/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp @@ -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; diff --git a/src/add-ons/kernel/bus_managers/ata/ATAPIDevice.cpp b/src/add-ons/kernel/bus_managers/ata/ATAPIDevice.cpp index 6e0ba8d858..2549e16439 100644 --- a/src/add-ons/kernel/bus_managers/ata/ATAPIDevice.cpp +++ b/src/add-ons/kernel/bus_managers/ata/ATAPIDevice.cpp @@ -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; } diff --git a/src/add-ons/kernel/bus_managers/ata/ATAPrivate.h b/src/add-ons/kernel/bus_managers/ata/ATAPrivate.h index 26d2ffc35d..542b3f044f 100644 --- a/src/add-ons/kernel/bus_managers/ata/ATAPrivate.h +++ b/src/add-ons/kernel/bus_managers/ata/ATAPrivate.h @@ -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]; };