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


ATADevice::ATADevice(ATAChannel *channel, uint8 index)
        :
        fChannel(channel),
        fRegisterMask(0),
        fUseDMA(channel->UseDMA()),
        fDMAMode(0),
        fDMAFailures(0),
        fTotalSectors(0),
        fBlockSize(512),
        fPhysicalBlockSize(512),
        fBlockOffset(0),
        fIndex(index),
        fUse48Bits(false)
{
        memset(&fInfoBlock, 0, sizeof(fInfoBlock));
        memset(&fTaskFile, 0, sizeof(fTaskFile));
}


ATADevice::~ATADevice()
{
}


status_t
ATADevice::TestUnitReady(ATARequest *request)
{
        TRACE_FUNCTION("%p\n", request);

        fRegisterMask = 0;
        fTaskFile.write.command = ATA_COMMAND_GET_MEDIA_STATUS;

        request->SetTimeout(15 * 1000 * 1000);
        status_t result = fChannel->SendRequest(request, ATA_DEVICE_READY_REQUIRED);
        if (result != B_OK) {
                TRACE_ERROR("failed to send test unit ready request\n");
                return result;
        }

        return fChannel->FinishRequest(request, ATA_WAIT_FINISH
                | ATA_DEVICE_READY_REQUIRED, ATA_ERROR_NO_MEDIA | ATA_ERROR_ABORTED
                | ATA_ERROR_MEDIA_CHANGE_REQUESTED | ATA_ERROR_MEDIUM_CHANGED);
}


status_t
ATADevice::SynchronizeCache(ATARequest *request)
{
        TRACE_FUNCTION("%p\n", request);

        // we should also ask for FLUSH CACHE support, but everyone denies it
        // (looks like they cheat to gain some performance advantage, but
        //  that's pretty useless: everyone does it...)
        if (!fInfoBlock.write_cache_supported)
                return B_OK;

        fRegisterMask = 0;
        fTaskFile.lba.command
                = fUse48Bits ? ATA_COMMAND_FLUSH_CACHE_EXT : ATA_COMMAND_FLUSH_CACHE;

        request->SetTimeout(60 * 1000 * 1000);
        status_t result = fChannel->SendRequest(request, ATA_DEVICE_READY_REQUIRED);
        if (result != B_OK) {
                TRACE_ERROR("failed to send synchronize cache request\n");
                return result;
        }

        return fChannel->FinishRequest(request, ATA_WAIT_FINISH
                | ATA_DEVICE_READY_REQUIRED, ATA_ERROR_ABORTED);
}


status_t
ATADevice::Eject(ATARequest *request)
{
        TRACE_FUNCTION("%p\n", request);

        fRegisterMask = 0;
        fTaskFile.lba.command = ATA_COMMAND_MEDIA_EJECT;

        request->SetTimeout(15 * 1000 * 1000);
        status_t result = fChannel->SendRequest(request, ATA_DEVICE_READY_REQUIRED);
        if (result != B_OK) {
                TRACE_ERROR("failed to send eject request\n");
                return result;
        }

        return fChannel->FinishRequest(request, ATA_WAIT_FINISH
                | ATA_DEVICE_READY_REQUIRED, ATA_ERROR_ABORTED | ATA_ERROR_NO_MEDIA);
}


status_t
ATADevice::Inquiry(ATARequest *request)
{
        TRACE_FUNCTION("%p\n", request);

        scsi_ccb *ccb = request->CCB();
        scsi_cmd_inquiry *command = (scsi_cmd_inquiry *)ccb->cdb;
        if (command->evpd || command->page_code) {
                request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_CDB_FIELD);
                return B_ERROR;
        }

        scsi_res_inquiry data;
        memset(&data, 0, sizeof(data));

        data.device_type = IsATAPI()
                ? fInfoBlock.word_0.atapi.command_packet_set : scsi_dev_direct_access;
        data.device_qualifier = scsi_periph_qual_connected;

        data.device_type_modifier = 0;
        data.removable_medium = fInfoBlock.word_0.ata.removable_media_device;

        data.ansi_version = 2;
        data.ecma_version = 0;
        data.iso_version = 0;

        data.response_data_format = 2;
        data.term_iop = false;
                // to be changed if we support TERM I/O

        data.additional_length = sizeof(scsi_res_inquiry) - 4;

        data.soft_reset = false;
        data.cmd_queue = 0;
        data.linked = false;

        // these values are free-style
        data.sync = false;
        data.write_bus16 = true;
        data.write_bus32 = false;

        data.relative_address = false;

        // the following fields are *much* to small, sigh...
        memcpy(data.vendor_ident, fInfoBlock.model_number,
                sizeof(data.vendor_ident));
        swap_words(data.vendor_ident, sizeof(data.vendor_ident));

        memcpy(data.product_ident, fInfoBlock.model_number + 8,
                sizeof(data.product_ident));
        swap_words(data.product_ident, sizeof(data.product_ident));

        memcpy(data.product_rev, "    ", sizeof(data.product_rev));

        uint32 allocationLength = command->allocation_length;
        copy_sg_data(ccb, 0, allocationLength, &data, sizeof(data), false);
        ccb->data_resid = ccb->data_length - MIN(MIN(sizeof(data),
                allocationLength), ccb->data_length);
        return B_OK;
}


status_t
ATADevice::ReadCapacity(ATARequest *request)
{
        TRACE_FUNCTION("%p\n", request);

        scsi_ccb *ccb = request->CCB();
        scsi_cmd_read_capacity *command = (scsi_cmd_read_capacity *)ccb->cdb;
        if (command->pmi || command->lba) {
                request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_CDB_FIELD);
                return B_ERROR;
        }

        scsi_res_read_capacity data;
        memset(&data, 0, sizeof(data));

        data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);

        if (fTotalSectors <= UINT_MAX) {
                uint32 lastBlock = fTotalSectors - 1;
                data.lba = B_HOST_TO_BENDIAN_INT32(lastBlock);
        } else
                data.lba = UINT_MAX;
        TRACE("returning last block: %" B_PRIu32 "\n",
                B_BENDIAN_TO_HOST_INT32(data.lba));

        copy_sg_data(ccb, 0, ccb->data_length, &data, sizeof(data), false);
        ccb->data_resid = MAX(ccb->data_length - sizeof(data), 0);
        return B_OK;
}


status_t
ATADevice::ReadCapacity16(ATARequest *request)
{
        TRACE_FUNCTION("%p\n", request);

        scsi_ccb *ccb = request->CCB();
        scsi_cmd_read_capacity_long *command
                = (scsi_cmd_read_capacity_long *)ccb->cdb;
        if (command->pmi || command->lba) {
                request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_CDB_FIELD);
                return B_ERROR;
        }

        uint32 allocationLength = B_BENDIAN_TO_HOST_INT32(command->alloc_length);

        scsi_res_read_capacity_long data;
        memset(&data, 0, sizeof(data));

        data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);

        uint64 lastBlock = fTotalSectors - 1;
        data.lba = B_HOST_TO_BENDIAN_INT64(lastBlock);
        TRACE("returning last block: %" B_PRIu64 "\n",
                B_BENDIAN_TO_HOST_INT64(data.lba));

        size_t copySize = min_c(allocationLength, sizeof(data));

        copy_sg_data(ccb, 0, ccb->data_length, &data, copySize, false);
        ccb->data_resid = MAX(ccb->data_length - copySize, 0);
        return B_OK;
}


status_t
ATADevice::ExecuteIO(ATARequest *request)
{
        TRACE_FUNCTION("%p\n", request);

        scsi_ccb *ccb = request->CCB();
        request->SetDevice(this);

        // ATA devices have one LUN only
        if (ccb->target_lun != 0) {
                TRACE_ERROR("invalid target lun %d for ATA device\n", ccb->target_lun);
                request->SetStatus(SCSI_SEL_TIMEOUT);
                return B_BAD_INDEX;
        }

        TRACE("request: 0x%02x\n", ccb->cdb[0]);

        switch (ccb->cdb[0]) {
                case SCSI_OP_TEST_UNIT_READY:
                        return TestUnitReady(request);

                case SCSI_OP_FORMAT: /* FORMAT UNIT */
                        // we could forward ccb to disk, but modern disks cannot
                        // be formatted anyway, so we just refuse ccb
                        // (exceptions are removable media devices, but to my knowledge
                        // they don't have to be formatted as well)
                        request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_OPCODE);
                        return B_ERROR;

                case SCSI_OP_INQUIRY:
                        return Inquiry(request);

                case SCSI_OP_START_STOP:
                {
                        scsi_cmd_ssu *command = (scsi_cmd_ssu *)ccb->cdb;

                        // with no LoEj bit set, we should only allow/deny further access
                        // we ignore that (unsupported for ATA)
                        // with LoEj bit set, we should additionally either load or eject
                        // the medium (start = 0 - eject; start = 1 - load)

                        if (!command->start) {
                                // we must always flush cache if start = 0
                                SynchronizeCache(request);
                        }

                        if (command->load_eject) {
                                if (!command->start)
                                        return Eject(request);
                                else {
                                        request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST,
                                                SCSIS_ASC_PARAM_NOT_SUPPORTED);
                                        return B_ERROR;
                                }
                        }

                        return B_OK;
                }

                case SCSI_OP_READ_CAPACITY:
                        return ReadCapacity(request);

                case SCSI_OP_SERVICE_ACTION_IN:
                        if ((ccb->cdb[1] & 0x1f) == SCSI_SAI_READ_CAPACITY_16)
                                return ReadCapacity16(request);
                        break;

                case SCSI_OP_SYNCHRONIZE_CACHE:
                        // we ignore range and immediate bit, we always immediately
                        // flush everything
                        return SynchronizeCache(request);

                // sadly, there are two possible read/write operation codes;
                // at least, the third one, read/write(12), is not valid for DAS
                case SCSI_OP_READ_6:
                case SCSI_OP_WRITE_6:
                {
                        scsi_cmd_rw_6 *command = (scsi_cmd_rw_6 *)ccb->cdb;
                        uint32 address = ((uint32)command->high_lba << 16)
                                | ((uint32)command->mid_lba << 8) | (uint32)command->low_lba;

                        request->SetIsWrite(command->opcode == SCSI_OP_WRITE_6);
                        return ExecuteReadWrite(request, address, command->length != 0
                                ? command->length : 256);
                }

                case SCSI_OP_READ_10:
                case SCSI_OP_WRITE_10:
                {
                        scsi_cmd_rw_10 *command = (scsi_cmd_rw_10 *)ccb->cdb;
                        uint32 address = B_BENDIAN_TO_HOST_INT32(command->lba);
                        uint32 sectorCount = B_BENDIAN_TO_HOST_INT16(command->length);

                        request->SetIsWrite(command->opcode == SCSI_OP_WRITE_10);
                        if (sectorCount > 0)
                                return ExecuteReadWrite(request, address, sectorCount);
                        else {
                                // we cannot transfer zero blocks (apart from LBA48)
                                request->SetStatus(SCSI_REQ_CMP);
                                return B_OK;
                        }
                }

                case SCSI_OP_READ_12:
                case SCSI_OP_WRITE_12:
                {
                        scsi_cmd_rw_12 *command = (scsi_cmd_rw_12 *)ccb->cdb;
                        uint32 address = B_BENDIAN_TO_HOST_INT32(command->lba);
                        uint32 sectorCount = B_BENDIAN_TO_HOST_INT32(command->length);

                        request->SetIsWrite(command->opcode == SCSI_OP_WRITE_12);
                        if (sectorCount > 0)
                                return ExecuteReadWrite(request, address, sectorCount);
                        else {
                                // we cannot transfer zero blocks (apart from LBA48)
                                request->SetStatus(SCSI_REQ_CMP);
                                return B_OK;
                        }
                }

                case SCSI_OP_READ_16:
                case SCSI_OP_WRITE_16:
                {
                        scsi_cmd_rw_16 *command = (scsi_cmd_rw_16 *)ccb->cdb;
                        uint64 address = B_BENDIAN_TO_HOST_INT64(command->lba);
                        uint32 sectorCount = B_BENDIAN_TO_HOST_INT32(command->length);

                        request->SetIsWrite(command->opcode == SCSI_OP_WRITE_16);
                        if (sectorCount > 0)
                                return ExecuteReadWrite(request, address, sectorCount);
                        else {
                                // we cannot transfer zero blocks (apart from LBA48)
                                request->SetStatus(SCSI_REQ_CMP);
                                return B_OK;
                        }
                }
        }

        TRACE("command not implemented\n");
        request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_OPCODE);
        return B_ERROR;
}


void
ATADevice::GetRestrictions(bool *noAutoSense, uint32 *maxBlocks)
{
        if (IsATAPI())
                *noAutoSense = true;
        else {
                if (fUse48Bits)
                        *maxBlocks = 0xffff;
                else
                        *maxBlocks = 0x100;
        }
}


status_t
ATADevice::Control(uint32 opcode, void *buffer, size_t length)
{
        if (opcode == B_GET_DEVICE_NAME) {
                // Swap words
                char name[sizeof(fInfoBlock.model_number)];
                memcpy(name, fInfoBlock.model_number, sizeof(name));
                swap_words(name, sizeof(name));

                // Remove trailing spaces
                int32 nameLength = sizeof(name) - 2;
                while (nameLength > 0 && name[nameLength - 1] == ' ')
                        nameLength--;

                // TODO: make string prettier, ie. "WDC" -> "Western Digital", ...
                return user_strlcpy((char*)buffer, name,
                        min_c((size_t)nameLength + 1, length)) >= 0 ? B_OK : B_BAD_ADDRESS;
        }
        return B_BAD_VALUE;
}


status_t
ATADevice::Select()
{
        status_t err = fChannel->SelectDevice(fIndex);
#if 1
    // for debugging only
        if (fChannel->SelectedDevice() != fIndex) {
                TRACE_ERROR("device %d not selected!\n", fIndex);
                return B_ERROR;
        }
#endif
        return err;
}


status_t
ATADevice::SetFeature(int feature)
{
        TRACE("device_set_feature: feature %d\n", feature);

        ATARequest request(false);
        request.SetDevice(this);
        request.SetTimeout(1 * 1000 * 1000);

        fTaskFile.write.features = feature;
        fTaskFile.write.command = ATA_COMMAND_SET_FEATURES;
        fRegisterMask = ATA_MASK_FEATURES;

        status_t result = fChannel->SendRequest(&request, ATA_DEVICE_READY_REQUIRED);
        if (result != B_OK) {
                TRACE_ERROR("sending set feature request failed\n");
                return result;
        }

        result = fChannel->FinishRequest(&request,
                ATA_WAIT_FINISH | ATA_DEVICE_READY_REQUIRED, ATA_ERROR_ABORTED);
        if (result != B_OK) {
                TRACE_ERROR("set feature request failed\n");
                return result;
        }

        return B_OK;
}


status_t
ATADevice::DisableCommandQueueing()
{
        if (!fInfoBlock.read_write_dma_queued_supported)
                return B_OK;

        if (fInfoBlock.release_interrupt_supported) {
                status_t result = SetFeature(
                        ATA_COMMAND_SET_FEATURES_DISABLE_RELEASE_INT);
                if (result != B_OK) {
                        TRACE_ERROR("failed to disable release interrupt\n");
                        return result;
                }
        }

        if (fInfoBlock.service_interrupt_supported) {
                status_t result = SetFeature(
                        ATA_COMMAND_SET_FEATURES_DISABLE_SERVICE_INT);
                if (result != B_OK) {
                        TRACE_ERROR("failed to disable service interrupt\n");
                        return result;
                }
        }

        return B_OK;
}


status_t
ATADevice::ConfigureDMA()
{
        if (!fUseDMA)
                return B_OK;

        if (!fInfoBlock.dma_supported) {
                TRACE_ALWAYS("DMA not supported by device\n");
                fUseDMA = false;
                return B_OK;
        }

        #define CHECK_DMA_MODE(element, mode) \
                if (fInfoBlock.element) { \
                        fDMAMode = mode; \
                        modeCount++; \
                }

        uint32 modeCount = 0;

        CHECK_DMA_MODE(multiword_dma_0_selected, 0x00);
        CHECK_DMA_MODE(multiword_dma_1_selected, 0x01);
        CHECK_DMA_MODE(multiword_dma_2_selected, 0x02);

        if (fInfoBlock.word_88_valid) {
                CHECK_DMA_MODE(ultra_dma_0_selected, 0x10);
                CHECK_DMA_MODE(ultra_dma_1_selected, 0x11);
                CHECK_DMA_MODE(ultra_dma_2_selected, 0x12);
                CHECK_DMA_MODE(ultra_dma_3_selected, 0x13);
                CHECK_DMA_MODE(ultra_dma_4_selected, 0x14);
                CHECK_DMA_MODE(ultra_dma_5_selected, 0x15);
                CHECK_DMA_MODE(ultra_dma_6_selected, 0x16);
        }

        #undef CHECK_DMA_MODE

        if (modeCount != 1) {
                TRACE_ERROR("more than one DMA mode selected, not using DMA\n");
                fUseDMA = false;
                return B_OK;
        }

        TRACE_ALWAYS("using DMA mode 0x%02x\n", fDMAMode);
        return B_OK;
}


status_t
ATADevice::Configure()
{
        // warning: ata == 0 means "this is ata"...
        if (fInfoBlock.word_0.ata.ata_device != ATA_WORD_0_ATA_DEVICE) {
                // CF has either magic header or CFA bit set
                // we merge it to "CFA bit set" for easier (later) testing
                if (fInfoBlock.word_0.raw == ATA_WORD_0_CFA_MAGIC)
                        fInfoBlock.compact_flash_assoc_supported = true;
                else {
                        TRACE_ERROR("infoblock indicates non-ata device\n");
                        return B_ERROR;
                }
        }

        if (!fInfoBlock.lba_supported || (fInfoBlock.lba_sector_count == 0
                && fInfoBlock.lba48_sector_count == 0)) {
                TRACE_ERROR("non-lba devices not supported\n");
                return B_ERROR;
        }

        fTotalSectors = fInfoBlock.SectorCount(fUse48Bits, false);
        fBlockSize = fInfoBlock.SectorSize();
        fPhysicalBlockSize = fInfoBlock.PhysicalSectorSize();
        fBlockOffset = fInfoBlock.BlockOffset();

        fTaskFile.lba.mode = ATA_MODE_LBA;
        fTaskFile.lba.device = fIndex;

        status_t result = ConfigureDMA();
        if (result != B_OK)
                return result;

        result = DisableCommandQueueing();
        if (result != B_OK)
                return result;

        return B_OK;
}


status_t
ATADevice::Identify()
{
        snprintf(fDebugContext, sizeof(fDebugContext), "%s %" B_PRIu32 "-%u",
                IsATAPI() ? "pi" : "", fChannel->ChannelID(), fIndex);

        ATARequest request(false);
        request.SetDevice(this);
        request.SetTimeout(20 * 1000 * 1000);

        fRegisterMask = 0;
        fTaskFile.write.command = IsATAPI() ? ATA_COMMAND_IDENTIFY_PACKET_DEVICE
                : ATA_COMMAND_IDENTIFY_DEVICE;

        if (fChannel->SendRequest(&request,
                        IsATAPI() ? 0 : ATA_DEVICE_READY_REQUIRED) != B_OK) {
                TRACE_ERROR("sending identify request failed\n");
                return B_ERROR;
        }

        if (fChannel->Wait(ATA_STATUS_BUSY | ATA_STATUS_DATA_REQUEST, 0,
                        ATA_WAIT_ANY_BIT, 100 * 1000) != B_OK) {
                TRACE_ALWAYS("no data request and not busy within 100ms, assuming "
                        "no device present\n");
                return B_TIMED_OUT;
        }

        if (fChannel->Wait(ATA_STATUS_DATA_REQUEST, ATA_STATUS_BUSY,
                        ATA_CHECK_ERROR_BIT | ATA_CHECK_DEVICE_FAULT,
                        IsATAPI() ? 20 * 1000 * 1000 : 500 * 1000) != B_OK) {
                TRACE_ERROR("timeout waiting for identify request\n");
                return B_TIMED_OUT;
        }

        // get the infoblock
        fChannel->ReadPIO((uint8 *)&fInfoBlock, sizeof(fInfoBlock));

        if (fChannel->WaitDataRequest(false) != B_OK) {
                TRACE_ERROR("device disagrees on info block length\n");
                return B_ERROR;
        }

        if (fChannel->FinishRequest(&request,
                        ATA_WAIT_FINISH | (IsATAPI() ? 0 : ATA_DEVICE_READY_REQUIRED),
                        ATA_ERROR_ABORTED) != B_OK) {
                TRACE_ERROR("failed to finish identify request\n");
                return B_ERROR;
        }

        if (1) {
                // print device information
                char modelNumber[sizeof(fInfoBlock.model_number) + 1];
                char serialNumber[sizeof(fInfoBlock.serial_number) + 1];
                char firmwareRev[sizeof(fInfoBlock.firmware_revision) + 1];
                strlcpy(modelNumber, fInfoBlock.model_number, sizeof(modelNumber));
                strlcpy(serialNumber, fInfoBlock.serial_number, sizeof(serialNumber));
                strlcpy(firmwareRev, fInfoBlock.firmware_revision, sizeof(firmwareRev));
                swap_words(modelNumber, sizeof(modelNumber) - 1);
                swap_words(serialNumber, sizeof(serialNumber) - 1);
                swap_words(firmwareRev, sizeof(firmwareRev) - 1);
                TRACE_ALWAYS("model number: %s\n", modelNumber);
                TRACE_ALWAYS("serial number: %s\n", serialNumber);
                TRACE_ALWAYS("firmware rev.: %s\n", firmwareRev);
        }

        return B_OK;
}


status_t
ATADevice::ExecuteReadWrite(ATARequest *request, uint64 address,
        uint32 sectorCount)
{
        request->SetUseDMA(fUseDMA && fChannel->PrepareDMA(request) == B_OK);
        if (!request->UseDMA())
                request->PrepareSGInfo();

        request->SetBytesLeft(sectorCount * fBlockSize);
        if (_FillTaskFile(request, address) != B_OK) {
                TRACE_ERROR("failed to setup transfer request\n");
                if (request->UseDMA())
                        fChannel->FinishDMA();
                return B_ERROR;
        }

        status_t result = fChannel->SendRequest(request,
                IsATAPI() ? 0 : ATA_DEVICE_READY_REQUIRED);
        if (result != B_OK) {
                TRACE_ERROR("failed to send transfer request\n");
                if (request->UseDMA())
                        fChannel->FinishDMA();
                return result;
        }

        if (request->UseDMA()) {
                fChannel->PrepareWaitingForInterrupt();
                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 {
                if (fChannel->Wait(ATA_STATUS_DATA_REQUEST, 0, ATA_CHECK_ERROR_BIT
                                | ATA_CHECK_DEVICE_FAULT, request->Timeout()) != B_OK) {
                        TRACE_ERROR("timeout waiting for device to request data\n");
                        request->SetStatus(SCSI_CMD_TIMEOUT);
                        return B_TIMED_OUT;
                }

                if (fChannel->ExecutePIOTransfer(request) != B_OK) {
                        TRACE_ERROR("executing pio transfer failed\n");
                        request->SetStatus(SCSI_SEQUENCE_FAIL);
                }
        }

        return fChannel->FinishRequest(request, ATA_WAIT_FINISH
                | ATA_DEVICE_READY_REQUIRED, ATA_ERROR_ALL);
}


status_t
ATADevice::_FillTaskFile(ATARequest *request, uint64 address)
{
        // list of LBA48 opcodes
        static const uint8 s48BitCommands[2][2] = {
                { ATA_COMMAND_READ_SECTORS_EXT, ATA_COMMAND_WRITE_SECTORS_EXT },
                { ATA_COMMAND_READ_DMA_EXT, ATA_COMMAND_WRITE_DMA_EXT }
        };

        // list of normal LBA opcodes
        static const uint8 s28BitCommands[2][2] = {
                { ATA_COMMAND_READ_SECTORS, ATA_COMMAND_WRITE_SECTORS },
                { ATA_COMMAND_READ_DMA, ATA_COMMAND_WRITE_DMA }
        };

        uint32 sectorCount = *request->BytesLeft() / fBlockSize;
        TRACE("about to transfer %lu sectors\n", sectorCount);

        if (fUse48Bits
                && (address + sectorCount > 0xfffffff || sectorCount > 0x100)) {
                // use LBA48 only if necessary
                if (sectorCount > 0xffff) {
                        TRACE_ERROR("invalid sector count %lu\n", sectorCount);
                        request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST,
                                SCSIS_ASC_INV_CDB_FIELD);
                        return B_ERROR;
                }

                fRegisterMask = ATA_MASK_SECTOR_COUNT_48
                        | ATA_MASK_LBA_LOW_48
                        | ATA_MASK_LBA_MID_48
                        | ATA_MASK_LBA_HIGH_48;

                fTaskFile.lba48.sector_count_0_7 = sectorCount & 0xff;
                fTaskFile.lba48.sector_count_8_15 = (sectorCount >> 8) & 0xff;
                fTaskFile.lba48.lba_0_7 = address & 0xff;
                fTaskFile.lba48.lba_8_15 = (address >> 8) & 0xff;
                fTaskFile.lba48.lba_16_23 = (address >> 16) & 0xff;
                fTaskFile.lba48.lba_24_31 = (address >> 24) & 0xff;
                fTaskFile.lba48.lba_32_39 = (address >> 32) & 0xff;
                fTaskFile.lba48.lba_40_47 = (address >> 40) & 0xff;
                fTaskFile.lba48.command = s48BitCommands[request->UseDMA()
                        ? 1 : 0][request->IsWrite() ? 1 : 0];
        } else {
                // normal LBA
                if (sectorCount > 0x100) {
                        TRACE_ERROR("invalid sector count %lu\n", sectorCount);
                        request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST,
                                SCSIS_ASC_INV_CDB_FIELD);
                        return B_ERROR;
                }

                fRegisterMask = ATA_MASK_SECTOR_COUNT
                        | ATA_MASK_LBA_LOW
                        | ATA_MASK_LBA_MID
                        | ATA_MASK_LBA_HIGH
                        | ATA_MASK_DEVICE_HEAD;

                fTaskFile.lba.sector_count = sectorCount & 0xff;
                fTaskFile.lba.lba_0_7 = address & 0xff;
                fTaskFile.lba.lba_8_15 = (address >> 8) & 0xff;
                fTaskFile.lba.lba_16_23 = (address >> 16) & 0xff;
                fTaskFile.lba.lba_24_27 = (address >> 24) & 0xf;
                fTaskFile.lba.command = s28BitCommands[request->UseDMA()
                        ? 1 : 0][request->IsWrite() ? 1 : 0];
        }

        return B_OK;
}