root/src/add-ons/kernel/bus_managers/ata/ATAChannel.cpp
/*
 * 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"


ATAChannel::ATAChannel(device_node *node)
        :
        fNode(node),
        fChannelID(0),
        fController(NULL),
        fCookie(NULL),
        fExpectsInterrupt(false),
        fStatus(B_NO_INIT),
        fSCSIBus(NULL),
        fDeviceCount(0),
        fDevices(NULL),
        fUseDMA(true),
        fRequest(NULL)
{
        B_INITIALIZE_SPINLOCK(&fInterruptLock);
        fInterruptCondition.Init(this, "ata dma transfer");

        gDeviceManager->get_attr_uint32(node, ATA_CHANNEL_ID_ITEM, &fChannelID,
                true);
        snprintf(fDebugContext, sizeof(fDebugContext), " %" B_PRIu32, fChannelID);

        if (fUseDMA) {
                if (get_safemode_boolean(B_SAFEMODE_DISABLE_IDE_DMA, false)) {
                        TRACE_ALWAYS("disabling DMA because of safemode setting\n");
                        fUseDMA = false;
                }
        }

        if (fUseDMA) {
                uint8 canDMA;
                if (gDeviceManager->get_attr_uint8(node, ATA_CONTROLLER_CAN_DMA_ITEM,
                        &canDMA, true) != B_OK) {
                        TRACE_ERROR("unknown if controller supports DMA, not using it\n");
                        fUseDMA = false;
                }

                if (canDMA == 0) {
                        TRACE_ALWAYS("controller doesn't support DMA, disabling\n");
                        fUseDMA = false;
                }
        }

        fRequest = new(std::nothrow) ATARequest(true);
        if (fRequest == NULL) {
                fStatus = B_NO_MEMORY;
                return;
        }

        uint8 maxDevices = 2;
        if (gDeviceManager->get_attr_uint8(node, ATA_CONTROLLER_MAX_DEVICES_ITEM,
                        &maxDevices, true) != B_OK) {
                maxDevices = 2;
        }

        fDeviceCount = MIN(maxDevices, 2);
        fDevices = new(std::nothrow) ATADevice *[fDeviceCount];
        if (fDevices == NULL) {
                fStatus = B_NO_MEMORY;
                return;
        }

        for (uint8 i = 0; i < fDeviceCount; i++)
                fDevices[i] = NULL;

        device_node *parent = gDeviceManager->get_parent_node(node);
        fStatus = gDeviceManager->get_driver(parent,
                (driver_module_info **)&fController, &fCookie);
        gDeviceManager->put_node(parent);

        fController->set_channel(fCookie, this);
}


ATAChannel::~ATAChannel()
{
        if (fDevices) {
                for (uint8 i = 0; i < fDeviceCount; i++)
                        delete fDevices[i];
                delete [] fDevices;
        }

        delete fRequest;
}


status_t
ATAChannel::InitCheck()
{
        return fStatus;
}


void
ATAChannel::SetBus(scsi_bus bus)
{
        fSCSIBus = bus;
}


bool
ATAChannel::_DevicePresent(int device)
{
        SelectDevice(device);

        if (SelectedDevice() != device) {
                TRACE_ALWAYS("_DevicePresent: device selection failed for device %i\n",
                device);
                return false;
        }

        ata_task_file taskFile;
        taskFile.chs.sector_count = 0x5a;
        taskFile.chs.sector_number = 0xa5;
        if (_WriteRegs(&taskFile, ATA_MASK_SECTOR_COUNT
                | ATA_MASK_SECTOR_NUMBER) != B_OK) {
                TRACE_ERROR("_DevicePresent: writing registers failed\n");
                return false;
        }
        if (_ReadRegs(&taskFile, ATA_MASK_SECTOR_COUNT
                | ATA_MASK_SECTOR_NUMBER) != B_OK) {
                TRACE_ERROR("_DevicePresent: reading registers failed\n");
                return false;
        }
        bool present = (taskFile.chs.sector_count == 0x5a &&
                taskFile.chs.sector_number == 0xa5);

        TRACE_ALWAYS("_DevicePresent: device %i, presence %d\n", device, present);
        return present;
}


status_t
ATAChannel::ScanBus()
{
        uint deviceMask = 0;

        for (int i = 0; i < fDeviceCount; i++)
                deviceMask |= (int)_DevicePresent(i) << i;

        status_t result = Reset();
        if (result != B_OK) {
                TRACE_ERROR("resetting the channel failed\n");
                return result;
        }

        TRACE_ALWAYS("deviceMask %d\n", deviceMask);

        for (int i = 0; i < fDeviceCount; i++) {
                if (!(deviceMask & (1 << i))) {
                        TRACE_ALWAYS("ignoring device %d\n", i);
                        continue;
                }

                TRACE_ALWAYS("probing device %d\n", i);
                SelectDevice(i);

                // ensure interrupts are disabled for this device
                _WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS);

                // wait up to 3 seconds for busy to clear
                if (Wait(0, ATA_STATUS_BUSY, 0, 3 * 1000 * 1000) != B_OK) {
                        uint8 status = AltStatus();
                        if (status == 0xff || status == 0x7f) {
                                TRACE_ALWAYS("illegal status value 0x%02x for device %d\n",
                                        status, i);
                                continue;
                        } else {
                                TRACE_ALWAYS("device %d is slow\n", i);
                        }
                }

                // wait up to 31 seconds for busy to clear (already 3 sec. waited)
                if (Wait(0, ATA_STATUS_BUSY, 0, 28 * 1000 * 1000) != B_OK) {
                        TRACE_ALWAYS("device %d reset timeout\n", i);
                        continue;
                }

                // reselect device
                SelectDevice(i);
                WaitForIdle();

                if (SelectedDevice() != i) {
                        TRACE_ALWAYS("device selection failed for device %i\n", i);
                        continue;
                }

                ata_task_file taskFile;
                if (_ReadRegs(&taskFile, ATA_MASK_LBA_MID | ATA_MASK_LBA_HIGH
                                | ATA_MASK_ERROR) != B_OK) {
                        TRACE_ERROR("reading status failed\n");
                        return B_ERROR;
                }

                // for information only
                if ((i == 0) && (taskFile.read.error & 0x80)) {
                        TRACE_ERROR("device 0 indicates that device 1 failed"
                                " error code is 0x%02x\n", taskFile.read.error);
                } else if (taskFile.read.error != 0x01) {
                        TRACE_ERROR("device %d failed, error code is 0x%02x\n",
                                i, taskFile.read.error);
                }

                uint16 signature = taskFile.lba.lba_8_15
                        | (((uint16)taskFile.lba.lba_16_23) << 8);
                TRACE_ALWAYS("signature of device %d: 0x%04x\n", i, signature);

                ATADevice *device = NULL;
                if (signature == ATA_SIGNATURE_ATAPI)
                        device = new(std::nothrow) ATAPIDevice(this, i);
                else
                        device = new(std::nothrow) ATADevice(this, i);

                if (device == NULL) {
                        TRACE_ERROR("out of memory allocating device\n");
                        return B_NO_MEMORY;
                }

                TRACE("trying ATA%s device %u\n", device->IsATAPI() ? "PI" : "", i);

                if (device->Identify() != B_OK) {
                        delete device;
                        continue;
                }

                if (device->Configure() != B_OK) {
                        TRACE_ERROR("failed to configure device\n");
                        delete device;
                        continue;
                }

                TRACE_ALWAYS("identified ATA%s device %u\n", device->IsATAPI()
                        ? "PI" : "", i);

                fDevices[i] = device;
        }

        return B_OK;
}


void
ATAChannel::PathInquiry(scsi_path_inquiry *info)
{
        info->hba_inquiry = SCSI_PI_TAG_ABLE | SCSI_PI_WIDE_16;
        info->hba_misc = 0;
        info->initiator_id = 2;
        info->hba_queue_size = 1;
        memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags));

        strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID);

        const char *controllerName = NULL;
        if (gDeviceManager->get_attr_string(fNode,
                        SCSI_DESCRIPTION_CONTROLLER_NAME, &controllerName, true) == B_OK)
                strlcpy(info->hba_vid, controllerName, SCSI_HBA_ID);
        else
                strlcpy(info->hba_vid, "unknown", SCSI_HBA_ID);

        strlcpy(info->sim_version, "1.0", SCSI_VERS);
        strlcpy(info->hba_version, "1.0", SCSI_VERS);
        strlcpy(info->controller_family, "ATA", SCSI_FAM_ID);
        strlcpy(info->controller_type, "ATA", SCSI_TYPE_ID);
}


void
ATAChannel::GetRestrictions(uint8 targetID, bool *isATAPI, bool *noAutoSense,
        uint32 *maxBlocks)
{
        // we always indicate ATAPI so we have to emulate fewer commands
        *isATAPI = true;
        *noAutoSense = false;
        *maxBlocks = 0x100;

        if (targetID < fDeviceCount && fDevices[targetID] != NULL)
                fDevices[targetID]->GetRestrictions(noAutoSense, maxBlocks);
}


status_t
ATAChannel::ExecuteIO(scsi_ccb *ccb)
{
        TRACE_FUNCTION("%p\n", ccb);
        status_t result = fRequest->Start(ccb);
        if (result != B_OK)
                return result;

        if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) {
                TRACE("request sense\n");
                fRequest->RequestSense();
                fRequest->Finish(false);
                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);
                fRequest->Finish(false);
                return B_BAD_INDEX;
        }

        ATADevice *device = fDevices[ccb->target_id];
        if (device == NULL) {
                TRACE_ERROR("target device not present\n");
                fRequest->SetStatus(SCSI_SEL_TIMEOUT);
                fRequest->Finish(false);
                return B_BAD_INDEX;
        }

        fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000
                : ATA_STANDARD_TIMEOUT);

        result = device->ExecuteIO(fRequest);
        fRequest->Finish(false);
        return result;
}


status_t
ATAChannel::Control(uint8 targetID, uint32 opcode, void *buffer, size_t length)
{
        if (targetID < fDeviceCount && fDevices[targetID] != NULL)
                return fDevices[targetID]->Control(opcode, buffer, length);

        return B_BAD_VALUE;
}


status_t
ATAChannel::SelectDevice(uint8 device)
{
        TRACE_FUNCTION("device: %u\n", device);

        if (device > 1)
                return B_BAD_INDEX;

        ata_task_file taskFile;
        taskFile.lba.lba_24_27 = 0;
        taskFile.lba.mode = ATA_MODE_LBA;
        taskFile.lba.device = device;

        status_t result = _WriteRegs(&taskFile, ATA_MASK_DEVICE_HEAD);
        if (result != B_OK) {
                TRACE_ERROR("writing register failed when trying to select device %d\n",
                        device);
                return result;
        }

        _FlushAndWait(1);

        return B_OK;
}


uint8
ATAChannel::SelectedDevice()
{
        ata_task_file taskFile;
        if (_ReadRegs(&taskFile, ATA_MASK_DEVICE_HEAD) != B_OK) {
                TRACE_ERROR("reading register failed when detecting selected device\n");
                // Return an invalid device number so that the
                // SelectedDevice() == "expected device" check fails.
                // Due to the device number being a bit, we can't really get values
                // other than 0 and 1, so anything >= 2 can be regarded as invalid.
                return 234;
        }

        return taskFile.lba.device;
}


status_t
ATAChannel::Reset()
{
        TRACE_FUNCTION("\n");

        SelectDevice(0);

        // disable interrupts and assert SRST for at least 5 usec
        if (_WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS
                | ATA_DEVICE_CONTROL_SOFT_RESET) != B_OK) {
                TRACE_ERROR("failed to set reset signaling\n");
                return B_ERROR;
        }

        _FlushAndWait(20);

        // clear reset and wait for at least 2 ms (wait 150ms like everyone else)
        if (_WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS) != B_OK) {
                TRACE_ERROR("failed to clear reset signaling\n");
                return B_ERROR;
        }

        _FlushAndWait(150 * 1000);

        // read status to clear any pending interrupts
        _Status();

        return B_OK;
}


status_t
ATAChannel::Wait(uint8 setBits, uint8 clearedBits, uint32 flags,
        bigtime_t timeout)
{
        bigtime_t startTime = system_time();
        _FlushAndWait(1);

        TRACE("wait for set bits 0x%02x and cleared bits 0x%02x\n",
                setBits, clearedBits);

#if ATA_TRACING
        unsigned lastStatus = 0x100;
#endif
        while (true) {
                uint8 status = AltStatus();
                if ((flags & ATA_CHECK_ERROR_BIT) != 0
                        && (status & ATA_STATUS_BUSY) == 0
                        && (status & ATA_STATUS_ERROR) != 0) {
                        TRACE("wait failed, error bit set, status 0x%02x\n", status);
                        return B_ERROR;
                }

                if ((flags & ATA_CHECK_DEVICE_FAULT) != 0
                        && (status & ATA_STATUS_BUSY) == 0
                        && (status & ATA_STATUS_DEVICE_FAULT) != 0) {
                        TRACE("wait failed, device fault bit set, status 0x%02x\n", status);
                        return B_ERROR;
                }

                if ((status & clearedBits) == 0) {
                        if ((flags & ATA_WAIT_ANY_BIT) != 0 && (status & setBits) != 0) {
                                TRACE("wait success, status 0x%02x\n", status);
                                return B_OK;
                        }
                        if ((status & setBits) == setBits) {
                                TRACE("wait success, status 0x%02x\n", status);
                                return B_OK;
                        }
                }

                bigtime_t elapsedTime = system_time() - startTime;
#if ATA_TRACING
                if (lastStatus != status) {
                        TRACE("wait status changed after %lld, status 0x%02x\n",
                                elapsedTime, status);
                        lastStatus = status;
                }
#endif

                if (elapsedTime > timeout) {
                        TRACE("wait timeout after %lld, status 0x%02x\n",
                                elapsedTime, status);
                        return B_TIMED_OUT;
                }

                // The device may be ready almost immediatelly. If it isn't,
                // poll often during the first 20ms, otherwise poll lazyly.
                if (elapsedTime < 1000)
                        spin(1);
                else if (elapsedTime < 20000)
                        snooze(1000);
                else
                        snooze(50000);
        }

        return B_ERROR;
}


status_t
ATAChannel::WaitDataRequest(bool high)
{
        return Wait(high ? ATA_STATUS_DATA_REQUEST : 0,
                high ? 0 : ATA_STATUS_DATA_REQUEST, 0, (high ? 10 : 1) * 1000 * 1000);
}


status_t
ATAChannel::WaitDeviceReady()
{
        return Wait(ATA_STATUS_DEVICE_READY, 0, 0, 5 * 1000 * 1000);
}


status_t
ATAChannel::WaitForIdle()
{
        return Wait(0, ATA_STATUS_BUSY | ATA_STATUS_DATA_REQUEST, 0, 50 * 1000);
}


status_t
ATAChannel::Interrupt(uint8 status)
{
        SpinLocker locker(fInterruptLock);
        if (!fExpectsInterrupt) {
                TRACE("interrupt when not expecting transfer\n");
                return B_UNHANDLED_INTERRUPT;
        }

        if ((status & ATA_STATUS_BUSY) != 0) {
                TRACE("interrupt while device is busy\n");
                return B_UNHANDLED_INTERRUPT;
        }

        TRACE("interrupt\n");

        fInterruptCondition.NotifyAll();
        return B_INVOKE_SCHEDULER;
}


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");
                result = RecoverLostInterrupt();
        }

        // disable interrupts
        _WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS);

        if (result != B_OK) {
                return B_TIMED_OUT;
        }

        return B_OK;
}


status_t
ATAChannel::RecoverLostInterrupt()
{
        // read status to clear any pending interrupts
        uint8 status = _Status();
        if (status & (ATA_STATUS_BUSY | ATA_STATUS_DATA_REQUEST)) {
                TRACE_ERROR("RecoverLostInterrupt: device busy, status 0x%02x\n", status);
                return B_ERROR;
        }
        TRACE_ERROR("RecoverLostInterrupt: lost interrupt, status 0x%02x\n", status);
        return B_OK;
}


status_t
ATAChannel::SendRequest(ATARequest *request, uint32 flags)
{
        // TODO: implement this:
        // resetting the device here would discard current configuration,
        // it's better when the SCSI bus manager requests an external reset.

        TRACE_FUNCTION("\n");
        ATADevice *device = request->Device();

        TRACE("SendRequest status 0x%02x\n", AltStatus());

        if (request->UseDMA())
                _WriteControl(0); // enable interrupts

        if (device->Select() != B_OK) {
                TRACE_ERROR("device selection failed\n");
                request->SetStatus(SCSI_SEL_TIMEOUT);
                _WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS);
                return B_TIMED_OUT;
        }

        if (WaitForIdle() != B_OK) {
                TRACE_ERROR("device selection timeout\n");
                request->SetStatus(SCSI_SEL_TIMEOUT);
                _WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS);
                return B_TIMED_OUT;
        }

        if ((flags & ATA_DEVICE_READY_REQUIRED) != 0
                && (AltStatus() & ATA_STATUS_DEVICE_READY) == 0) {
                TRACE_ERROR("device ready not set\n");
                request->SetStatus(SCSI_SEQUENCE_FAIL);
                _WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS);
                return B_ERROR;
        }

        if (_WriteRegs(device->TaskFile(), device->RegisterMask()
                        | ATA_MASK_COMMAND) != B_OK) {
                TRACE_ERROR("can't write command\n");
                request->SetStatus(SCSI_HBA_ERR);
                _WriteControl(ATA_DEVICE_CONTROL_DISABLE_INTS);
                return B_ERROR;
        }

        return B_OK;
}


status_t
ATAChannel::FinishRequest(ATARequest *request, uint32 flags, uint8 errorMask)
{
        TRACE_FUNCTION("\n");
        if (flags & ATA_WAIT_FINISH) {
                // wait for the device to finish current command (device no longer busy)
                status_t result = Wait(0, ATA_STATUS_BUSY, flags, request->Timeout());
                if (result != B_OK) {
                        TRACE_ERROR("timeout waiting for request finish\n");
                        request->SetStatus(SCSI_CMD_TIMEOUT);
                        return result;
                }
        }

        ata_task_file *taskFile = request->Device()->TaskFile();

        // read status, this also acknowledges pending interrupts
        status_t result = _ReadRegs(taskFile, ATA_MASK_STATUS | ATA_MASK_ERROR);
        if (result != B_OK) {
                TRACE("reading status failed\n");
                request->SetStatus(SCSI_SEQUENCE_FAIL);
                return result;
        }

        if (taskFile->read.status & ATA_STATUS_BUSY) {
                TRACE("command failed, device still busy\n");
                request->SetStatus(SCSI_SEQUENCE_FAIL);
                return B_ERROR;
        }

        if ((flags & ATA_DEVICE_READY_REQUIRED)
                && (taskFile->read.status & ATA_STATUS_DEVICE_READY) == 0) {
                TRACE("command failed, device ready required but not set\n");
                request->SetStatus(SCSI_SEQUENCE_FAIL);
                return B_ERROR;
        }

        uint8 checkFlags = ATA_STATUS_ERROR;
        if (flags & ATA_CHECK_DEVICE_FAULT)
                checkFlags |= ATA_STATUS_DEVICE_FAULT;

        if ((taskFile->read.status & checkFlags) == 0)
                return B_OK;

        if ((taskFile->read.error & ATA_ERROR_MEDIUM_CHANGED)
                        != ATA_ERROR_MEDIUM_CHANGED) {
                TRACE_ERROR("command failed, error bit is set. status 0x%02x, error 0x%02x\n",
                        taskFile->read.status, taskFile->read.error);
        }

        uint8 error = taskFile->read.error & errorMask;
        if (error & ATA_ERROR_INTERFACE_CRC) {
                TRACE_ERROR("interface crc error\n");
                request->SetSense(SCSIS_KEY_HARDWARE_ERROR, SCSIS_ASC_LUN_COM_CRC);
                return B_ERROR;
        }

        if (request->IsWrite()) {
                if (error & ATA_ERROR_WRITE_PROTECTED) {
                        request->SetSense(SCSIS_KEY_DATA_PROTECT, SCSIS_ASC_WRITE_PROTECTED);
                        return B_ERROR;
                }
        } else {
                if (error & ATA_ERROR_UNCORRECTABLE) {
                        request->SetSense(SCSIS_KEY_MEDIUM_ERROR, SCSIS_ASC_UNREC_READ_ERR);
                        return B_ERROR;
                }
        }

        if (error & ATA_ERROR_MEDIUM_CHANGED) {
                request->SetSense(SCSIS_KEY_UNIT_ATTENTION, SCSIS_ASC_MEDIUM_CHANGED);
                return B_ERROR;
        }

        if (error & ATA_ERROR_INVALID_ADDRESS) {
                // XXX strange error code, don't really know what it means
                request->SetSense(SCSIS_KEY_MEDIUM_ERROR, SCSIS_ASC_RANDOM_POS_ERROR);
                return B_ERROR;
        }

        if (error & ATA_ERROR_MEDIA_CHANGE_REQUESTED) {
                request->SetSense(SCSIS_KEY_UNIT_ATTENTION, SCSIS_ASC_REMOVAL_REQUESTED);
                return B_ERROR;
        }

        if (error & ATA_ERROR_NO_MEDIA) {
                request->SetSense(SCSIS_KEY_MEDIUM_ERROR, SCSIS_ASC_NO_MEDIUM);
                return B_ERROR;
        }

        if (error & ATA_ERROR_ABORTED) {
                request->SetSense(SCSIS_KEY_ABORTED_COMMAND, SCSIS_ASC_NO_SENSE);
                return B_ERROR;
        }

        // either there was no error bit set or it was masked out
        request->SetSense(SCSIS_KEY_HARDWARE_ERROR, SCSIS_ASC_INTERNAL_FAILURE);
        return B_ERROR;
}


status_t
ATAChannel::PrepareDMA(ATARequest *request)
{
        scsi_ccb *ccb = request->CCB();
        return fController->prepare_dma(fCookie, ccb->sg_list, ccb->sg_count,
                request->IsWrite());
}


status_t
ATAChannel::StartDMA()
{
        return fController->start_dma(fCookie);
}


status_t
ATAChannel::FinishDMA()
{
        return fController->finish_dma(fCookie);
}


status_t
ATAChannel::ExecutePIOTransfer(ATARequest *request)
{
        bigtime_t timeout = request->Timeout();
        status_t result = B_OK;
        size_t *bytesLeft = request->BytesLeft();
        while (*bytesLeft > 0) {
                size_t currentLength = MIN(*bytesLeft, request->Device()->BlockSize());
                if (request->IsWrite()) {
                        result = _WritePIOBlock(request, currentLength);
                        if (result != B_OK) {
                                TRACE_ERROR("failed to write pio block\n");
                                break;
                        }
                } else {
                        result = _ReadPIOBlock(request, currentLength);
                        if (result != B_OK) {
                                TRACE_ERROR("failed to read pio block\n");
                                break;
                        }
                }

                *bytesLeft -= currentLength;

                if (*bytesLeft > 0) {
                        // wait for next block to be ready
                        if (Wait(ATA_STATUS_DATA_REQUEST, ATA_STATUS_BUSY,
                                ATA_CHECK_ERROR_BIT | ATA_CHECK_DEVICE_FAULT,
                                timeout) != B_OK) {
                                TRACE_ERROR("timeout waiting for device to request data\n");
                                result = B_TIMED_OUT;
                                break;
                        }
                }
        }

        if (result == B_OK && WaitDataRequest(false) != B_OK) {
                TRACE_ERROR("device still expects data transfer\n");
                result = B_ERROR;
        }

        return result;
}


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)
{
        return fController->read_pio(fCookie, (uint16 *)buffer,
                length / sizeof(uint16), false);
}


status_t
ATAChannel::WritePIO(uint8 *buffer, size_t length)
{
        return fController->write_pio(fCookie, (uint16 *)buffer,
                length / sizeof(uint16), true);
}


status_t
ATAChannel::_ReadRegs(ata_task_file *taskFile, ata_reg_mask mask)
{
        return fController->read_command_block_regs(fCookie, taskFile, mask);
}


status_t
ATAChannel::_WriteRegs(ata_task_file *taskFile, ata_reg_mask mask)
{
        return fController->write_command_block_regs(fCookie, taskFile, mask);
}


uint8
ATAChannel::_Status()
{
        ata_task_file taskFile;
        if (_ReadRegs(&taskFile, ATA_MASK_STATUS) != B_OK)
                return 0x01;
        return taskFile.read.status;
}


status_t
ATAChannel::_WriteControl(uint8 value)
{
        return fController->write_device_control(fCookie, ATA_DEVICE_CONTROL_BIT3
                | value);
}


void
ATAChannel::_FlushAndWait(bigtime_t waitTime)
{
        AltStatus();
        if (waitTime > 100)
                snooze(waitTime);
        else
                spin(waitTime);
}


status_t
ATAChannel::_ReadPIOBlock(ATARequest *request, size_t length)
{
        size_t transferred = 0;
        status_t result = _TransferPIOBlock(request, length, &transferred);
        request->CCB()->data_resid -= transferred;

        // if length was odd, there's an extra byte waiting in request->OddByte()
        if (request->GetOddByte(NULL)) {
                // discard byte and adjust res_id as the extra byte didn't reach the
                // buffer
                request->CCB()->data_resid++;
        }

        if (result != B_BUFFER_OVERFLOW)
                return result;

        // the device returns more data then the buffer can store;
        // for ATAPI this is OK - we just discard remaining bytes (there
        // is no way to tell ATAPI about that, but we "only" waste time)

        // perhaps discarding the extra odd-byte was sufficient
        if (transferred >= length)
                return B_OK;

        TRACE_ERROR("pio read: discarding after %lu bytes\n", transferred);

        uint8 buffer[32];
        length -= transferred;
        // discard 32 bytes at once     (see _WritePIOBlock())
        while (length > 0) {
                // read extra byte if length is odd (that's the "length + 1")
                size_t currentLength = MIN(length + 1, (uint32)sizeof(buffer))
                        / sizeof(uint16);
                fController->read_pio(fCookie, (uint16 *)buffer, currentLength, false);
                length -= currentLength * 2;
        }

        return B_OK;
}


status_t
ATAChannel::_WritePIOBlock(ATARequest *request, size_t length)
{
        size_t transferred = 0;
        status_t result = _TransferPIOBlock(request, length, &transferred);
        request->CCB()->data_resid -= transferred;

        if (result != B_BUFFER_OVERFLOW)
                return result;

        // there may be a pending odd byte - transmit that now
        uint8 byte;
        if (request->GetOddByte(&byte)) {
                uint8 buffer[2];
                buffer[0] = byte;
                buffer[1] = 0;

                fController->write_pio(fCookie, (uint16 *)buffer, 1, false);
                request->CCB()->data_resid--;
                transferred += 2;
        }

        // "transferred" may actually be larger then length because the last odd-byte
        // is sent together with an extra zero-byte
        if (transferred >= length)
                return B_OK;

        // Ouch! the device asks for data but we haven't got any left.
        // Sadly, this behaviour is OK for ATAPI packets, but there is no
        // way to tell the device that we don't have any data left;
        // only solution is to send zero bytes, though it's BAD
        static const uint8 buffer[32] = {};

        TRACE_ERROR("pio write: discarding after %lu bytes\n", transferred);

        length -= transferred;
        while (length > 0) {
                // if device asks for odd number of bytes, append an extra byte to
                // make length even (this is the "length + 1" term)
                size_t currentLength = MIN(length + 1, (int)(sizeof(buffer)))
                        / sizeof(uint16);
                fController->write_pio(fCookie, (uint16 *)buffer, currentLength, false);
                length -= currentLength * 2;
        }

        return B_BUFFER_OVERFLOW;
}


status_t
ATAChannel::_TransferPIOBlock(ATARequest *request, size_t length,
        size_t *transferred)
{
        // data is usually split up into multiple scatter/gather blocks
        while (length > 0) {
                if (request->SGElementsLeft() == 0) {
                        // ups - buffer too small (for ATAPI data, this is OK)
                        return B_BUFFER_OVERFLOW;
                }

                // we might have transmitted part of a scatter/entry already
                const physical_entry *entry = request->CurrentSGElement();
                uint32 offset = request->CurrentSGOffset();
                uint32 currentLength = MIN(entry->size - offset, length);

                status_t result = _TransferPIOPhysical(request,
                        entry->address + offset, currentLength, transferred);
                if (result != B_OK) {
                        request->SetSense(SCSIS_KEY_HARDWARE_ERROR,
                                SCSIS_ASC_INTERNAL_FAILURE);
                        return result;
                }

                request->AdvanceSG(currentLength);
                length -= currentLength;
        }

        return B_OK;
}


// TODO: this should not be necessary, we could directly use virtual addresses
#include <vm/vm.h>
#include <thread.h>

status_t
ATAChannel::_TransferPIOPhysical(ATARequest *request, addr_t physicalAddress,
        size_t length, size_t *transferred)
{
        // we must split up chunk into B_PAGE_SIZE blocks as we can map only
        // one page into address space at once
        while (length > 0) {
                Thread *thread = thread_get_current_thread();
                thread_pin_to_current_cpu(thread);

                void *handle;
                addr_t virtualAddress;
                if (vm_get_physical_page_current_cpu(physicalAddress, &virtualAddress,
                                &handle) != B_OK) {
                        thread_unpin_from_current_cpu(thread);
                        // ouch: this should never ever happen
                        return B_ERROR;
                }

                ASSERT(physicalAddress % B_PAGE_SIZE == virtualAddress % B_PAGE_SIZE);

                // if chunk starts in the middle of a page, we have even less then
                // a page left
                size_t pageLeft = B_PAGE_SIZE - physicalAddress % B_PAGE_SIZE;
                size_t currentLength = MIN(pageLeft, length);

                status_t result = _TransferPIOVirtual(request, (uint8 *)virtualAddress,
                        currentLength, transferred);

                vm_put_physical_page_current_cpu(virtualAddress, handle);
                thread_unpin_from_current_cpu(thread);

                if (result != B_OK)
                        return result;

                length -= currentLength;
                physicalAddress += currentLength;
        }

        return B_OK;
}


status_t
ATAChannel::_TransferPIOVirtual(ATARequest *request, uint8 *virtualAddress,
        size_t length, size_t *transferred)
{
        if (request->IsWrite()) {
                // if there is a byte left from last chunk, transmit it together
                // with the first byte of the current chunk (IDE requires 16 bits
                // to be transmitted at once)
                uint8 byte;
                if (request->GetOddByte(&byte)) {
                        uint8 buffer[2];

                        buffer[0] = byte;
                        buffer[1] = *virtualAddress++;

                        fController->write_pio(fCookie, (uint16 *)buffer, 1, false);

                        length--;
                        *transferred += 2;
                }

                fController->write_pio(fCookie, (uint16 *)virtualAddress, length / 2,
                        false);

                // take care if chunk size was odd, which means that 1 byte remains
                virtualAddress += length & ~1;
                *transferred += length & ~1;

                if ((length & 1) != 0)
                        request->SetOddByte(*virtualAddress);
        } else {
                // if we read one byte too much last time, push it into current chunk
                uint8 byte;
                if (request->GetOddByte(&byte)) {
                        *virtualAddress++ = byte;
                        length--;
                }

                fController->read_pio(fCookie, (uint16 *)virtualAddress, length / 2,
                        false);

                // take care of odd chunk size;
                // in this case we read 1 byte to few!
                virtualAddress += length & ~1;
                *transferred += length & ~1;

                if ((length & 1) != 0) {
                        uint8 buffer[2];

                        // now read the missing byte; as we have to read 2 bytes at once,
                        // we'll read one byte too much
                        fController->read_pio(fCookie, (uint16 *)buffer, 1, false);

                        *virtualAddress = buffer[0];
                        request->SetOddByte(buffer[1]);

                        *transferred += 2;
                }
        }

        return B_OK;
}