Files
haiku-beta6/src/add-ons/kernel/bus_managers/ata/ATAChannel.cpp
T
Michael Lotz 118bb4e750 * Fix race condition between finishing a request and returning a busy status
when the channel is in use, which would cause the SCSI scheduler to stop
  sending requests.
* Indeed we need to return a status from the interrupt handler, as for PIO
  transfers there is no way of knowning whether or not the interrupt at hand
  was ours.
* Add some debug output.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30091 a95241bf-73f2-0310-859d-f6bbb57e9c96
2009-04-10 00:14:07 +00:00

991 lines
24 KiB
C++

/*
* Copyright 2009, Michael Lotz, [email protected].
* Copyright 2008, Marcus Overhagen.
* Copyright 2004-2008, Axel Dörfler, [email protected].
* 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)
{
mutex_init(&fExecutionLock, "ata io execution");
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), " %lu", fChannelID);
if (fUseDMA) {
void *settings = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS);
if (settings != NULL) {
if (get_driver_boolean_parameter(settings,
B_SAFEMODE_DISABLE_IDE_DMA, false, false)) {
TRACE_ALWAYS("disabling DMA because of safemode setting\n");
fUseDMA = false;
}
unload_driver_settings(settings);
}
}
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();
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()
{
mutex_lock(&fExecutionLock);
if (fDevices) {
for (uint8 i = 0; i < fDeviceCount; i++)
delete fDevices[i];
delete [] fDevices;
}
delete fRequest;
mutex_destroy(&fExecutionLock);
}
status_t
ATAChannel::InitCheck()
{
return fStatus;
}
void
ATAChannel::SetBus(scsi_bus bus)
{
fSCSIBus = bus;
}
status_t
ATAChannel::ScanBus()
{
bool devicePresent[fDeviceCount];
uint32 deviceSignature[fDeviceCount];
status_t result = Reset(devicePresent, deviceSignature);
if (result != B_OK) {
TRACE_ERROR("resetting the channel failed\n");
return result;
}
for (uint8 i = 0; i < fDeviceCount; i++) {
if (!devicePresent[i])
continue;
ATADevice *device = NULL;
if (deviceSignature[i] == ATA_SIGNATURE_ATAPI)
device = new(std::nothrow) ATAPIDevice(this, i);
else
device = new(std::nothrow) ATADevice(this, i);
if (device == NULL)
return B_NO_MEMORY;
TRACE("trying ATA%s device %u\n", device->IsATAPI() ? "PI" : "", i);
bool identified = device->Identify() == B_OK;
if (!identified && !device->IsATAPI()) {
// retry as atapi
delete device;
device = new(std::nothrow) ATAPIDevice(this, i);
if (device == NULL)
return B_NO_MEMORY;
identified = device->Identify() == B_OK;
}
if (!identified) {
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->sim_priv = 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;
if (targetID < fDeviceCount && fDevices[targetID] != NULL
&& fDevices[targetID]->IsATAPI()) {
*noAutoSense = true;
}
*maxBlocks = 255;
}
status_t
ATAChannel::ExecuteIO(scsi_ccb *ccb)
{
TRACE_FUNCTION("%p\n", ccb);
if (mutex_trylock(&fExecutionLock) != B_OK) {
TRACE("channel is busy\n");
return B_BUSY;
}
fRequest->SetCCB(ccb);
if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE) {
TRACE("request sense\n");
fRequest->RequestSense();
fRequest->Finish(false, &fExecutionLock);
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, &fExecutionLock);
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, &fExecutionLock);
return B_BAD_INDEX;
}
fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000
: ATA_STANDARD_TIMEOUT);
status_t result = device->ExecuteIO(fRequest);
fRequest->Finish(false, &fExecutionLock);
return result;
}
status_t
ATAChannel::SelectDevice(uint8 device)
{
TRACE_FUNCTION("device: %u\n", device);
if (device > 1)
return B_BAD_INDEX;
ata_task_file taskFile;
taskFile.chs.head = 0;
taskFile.chs.mode = ATA_MODE_LBA;
taskFile.chs.device = device;
_WriteRegs(&taskFile, ATA_MASK_DEVICE_HEAD);
_FlushAndWait(1);
#if KDEBUG > 0
// for debugging only
_ReadRegs(&taskFile, ATA_MASK_DEVICE_HEAD);
if (taskFile.chs.device != device) {
TRACE_ERROR("device %d not selected! head 0x%x, mode 0x%x, device %d\n",
device, taskFile.chs.head, taskFile.chs.mode, taskFile.chs.device);
return B_ERROR;
}
#endif
return B_OK;
}
bool
ATAChannel::IsDevicePresent(uint8 device)
{
if (SelectDevice(device) != B_OK)
return false;
ata_task_file taskFile;
taskFile.chs.device = device;
taskFile.chs.mode = ATA_MODE_LBA;
taskFile.chs.command = ATA_COMMAND_NOP;
_WriteRegs(&taskFile, ATA_MASK_DEVICE_HEAD);
_FlushAndWait(10);
_ReadRegs(&taskFile, ATA_MASK_STATUS | ATA_MASK_ERROR);
TRACE("status: 0x%02x; error: 0x%02x\n", taskFile.read.status,
taskFile.read.error);
return (taskFile.read.status & 0xf8) != 0xf8
&& taskFile.read.status != 0xa5;
}
status_t
ATAChannel::Reset(bool *presence, uint32 *signatures)
{
TRACE_FUNCTION("%p, %p\n", presence, signatures);
bool devicePresent[fDeviceCount];
for (uint8 i = 0; i < fDeviceCount; i++) {
devicePresent[i] = IsDevicePresent(i);
TRACE("device %d: %s present\n", i, devicePresent[i] ? "might be" : "is not");
}
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);
for (uint8 i = 0; i < fDeviceCount; i++) {
if (presence != NULL)
presence[i] = devicePresent[i];
if (!devicePresent[i])
continue;
SelectDevice(i);
// wait up to 31 seconds for busy to clear
if (Wait(0, ATA_STATUS_BUSY, 0, 31 * 1000 * 1000) != B_OK) {
TRACE_ERROR("reset timeout\n");
return B_TIMED_OUT;
}
ata_task_file taskFile;
if (_ReadRegs(&taskFile, ATA_MASK_SECTOR_COUNT | ATA_MASK_LBA_LOW
| ATA_MASK_LBA_MID | ATA_MASK_LBA_HIGH | ATA_MASK_ERROR) != B_OK) {
TRACE_ERROR("reading status failed\n");
return B_ERROR;
}
if (taskFile.read.error != 0x01
&& (i > 0 || taskFile.read.error != 0x81)) {
TRACE_ERROR("device %d failed, error code is 0x%02x\n", i,
taskFile.read.error);
}
if (i == 0 && taskFile.read.error >= 0x80) {
TRACE_ERROR("device %d indicates that other device failed"
" with code 0x%02x\n", i, taskFile.read.error);
}
if (signatures != NULL) {
signatures[i] = taskFile.lba.sector_count
| (((uint32)taskFile.lba.lba_0_7) << 8)
| (((uint32)taskFile.lba.lba_8_15) << 16)
| (((uint32)taskFile.lba.lba_16_23) << 24);
}
}
return B_OK;
}
status_t
ATAChannel::Wait(uint8 setBits, uint8 clearedBits, uint32 flags,
bigtime_t timeout)
{
bigtime_t startTime = system_time();
_FlushAndWait(1);
while (true) {
uint8 status = AltStatus();
if ((flags & ATA_CHECK_ERROR_BIT) != 0
&& (status & ATA_STATUS_ERROR) != 0)
return B_ERROR;
if ((status & clearedBits) == 0) {
if ((flags & ATA_WAIT_ANY_BIT) != 0 && (status & setBits) != 0)
return B_OK;
if ((status & setBits) == setBits)
return B_OK;
}
bigtime_t elapsedTime = system_time() - startTime;
//TRACE("wait status after %lld: %u\n", elapsedTime, status);
if (elapsedTime > timeout)
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 < 500)
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,
ATA_CHECK_ERROR_BIT, (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);
}
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;
}
status_t
ATAChannel::SendRequest(ATARequest *request, uint32 flags)
{
// disable interrupts for PIO transfers, enable them for DMA
_WriteControl((flags & ATA_DMA_TRANSFER) != 0 ? 0
: ATA_DEVICE_CONTROL_DISABLE_INTS);
ATADevice *device = request->Device();
if (device->Select() != B_OK || WaitForIdle() != B_OK) {
// resetting the device here will discard current configuration,
// it's better when the SCSI bus manager requests an external reset.
TRACE_ERROR("device selection timeout\n");
request->SetStatus(SCSI_SEL_TIMEOUT);
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);
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);
return B_ERROR;
}
return B_OK;
}
status_t
ATAChannel::FinishRequest(ATARequest *request, uint32 flags, uint8 errorMask)
{
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, 0, 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;
}
if ((taskFile->read.status & ATA_STATUS_ERROR) == 0)
return B_OK;
request->SetStatus(SCSI_SEQUENCE_FAIL);
TRACE_ERROR("command failed, error bit is set: 0x%02x\n",
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 (flags & ATA_IS_WRITE) {
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;
uint32 *blocksLeft = request->BlocksLeft();
while (*blocksLeft > 0) {
if (Wait(ATA_STATUS_DATA_REQUEST, ATA_STATUS_BUSY, 0, timeout) != B_OK) {
TRACE_ERROR("timeout waiting for device to request data\n");
result = B_TIMED_OUT;
break;
}
if (request->IsWrite()) {
result = _WritePIOBlock(request, 512);
if (result != B_OK) {
TRACE_ERROR("failed to write pio block\n");
break;
}
} else {
result = _ReadPIOBlock(request, 512);
if (result != B_OK) {
TRACE_ERROR("failed to read pio block\n");
break;
}
}
(*blocksLeft)--;
// wait 1 pio cycle
if (*blocksLeft > 0)
AltStatus();
}
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::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;
}
fInterruptCondition.NotifyAll();
return B_HANDLED_INTERRUPT;
}
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);
}
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)
{
uint32 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,
(addr_t)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.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) {
struct 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;
}