mirror of
https://github.com/reactos/reactos.git
synced 2026-05-30 23:33:24 +08:00
[FREELDR:HWIDE] Improve support for ATAPI devices (#8560)
- Adjust DRQ timeout to fix some slow ATAPI devices. - Add a fix for devices that clear BSY before raising the DRQ bit. - Enable use of 32-bit I/O on Xbox. CORE-17977 CORE-16216
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* PROJECT: FreeLoader
|
||||
* LICENSE: MIT (https://spdx.org/licenses/MIT)
|
||||
* PURPOSE: ATA/ATAPI programmed I/O driver.
|
||||
* COPYRIGHT: Copyright 2019-2025 Dmitry Borisov (di.sean@protonmail.com)
|
||||
* COPYRIGHT: Copyright 2019-2026 Dmitry Borisov <di.sean@protonmail.com>
|
||||
*/
|
||||
|
||||
/* INCLUDES *******************************************************************/
|
||||
@@ -38,21 +38,6 @@ static PHW_DEVICE_UNIT AtapUnits[CHANNEL_MAX_CHANNELS * CHANNEL_MAX_DEVICES];
|
||||
|
||||
/* PRIVATE FUNCTIONS **********************************************************/
|
||||
|
||||
#if defined(ATA_SUPPORT_32_BIT_IO)
|
||||
static
|
||||
inline
|
||||
BOOLEAN
|
||||
AtapIs32BitIoSupported(
|
||||
_In_ PHW_DEVICE_UNIT DeviceUnit)
|
||||
{
|
||||
#if defined(ATA_ALWAYS_DO_32_BIT_IO)
|
||||
return TRUE;
|
||||
#else
|
||||
return !!(DeviceUnit->P.Flags & ATA_DEVICE_FLAG_IO32);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static
|
||||
VOID
|
||||
AtapSelectDevice(
|
||||
@@ -69,11 +54,12 @@ AtapSelectDevice(
|
||||
}
|
||||
|
||||
static
|
||||
BOOLEAN
|
||||
AtapWaitForNotBusy(
|
||||
UCHAR
|
||||
AtapWait(
|
||||
_In_ PIDE_REGISTERS Registers,
|
||||
_In_range_(>, 0) ULONG Timeout,
|
||||
_Out_opt_ PUCHAR Result)
|
||||
_In_ UCHAR Mask,
|
||||
_In_ UCHAR Value)
|
||||
{
|
||||
UCHAR IdeStatus;
|
||||
ULONG i;
|
||||
@@ -83,12 +69,8 @@ AtapWaitForNotBusy(
|
||||
for (i = 0; i < Timeout; ++i)
|
||||
{
|
||||
IdeStatus = ATA_READ(Registers->Status);
|
||||
if (!(IdeStatus & IDE_STATUS_BUSY))
|
||||
{
|
||||
if (Result)
|
||||
*Result = IdeStatus;
|
||||
return TRUE;
|
||||
}
|
||||
if ((IdeStatus & Mask) == Value)
|
||||
break;
|
||||
|
||||
if (IdeStatus == 0xFF)
|
||||
break;
|
||||
@@ -96,34 +78,7 @@ AtapWaitForNotBusy(
|
||||
StallExecutionProcessor(10);
|
||||
}
|
||||
|
||||
if (Result)
|
||||
*Result = IdeStatus;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static
|
||||
BOOLEAN
|
||||
AtapWaitForIdle(
|
||||
_In_ PIDE_REGISTERS Registers,
|
||||
_Out_ PUCHAR Result)
|
||||
{
|
||||
UCHAR IdeStatus;
|
||||
ULONG i;
|
||||
|
||||
for (i = 0; i < ATA_TIME_DRQ_CLEAR; ++i)
|
||||
{
|
||||
IdeStatus = ATA_READ(Registers->Status);
|
||||
if (!(IdeStatus & (IDE_STATUS_DRQ | IDE_STATUS_BUSY)))
|
||||
{
|
||||
*Result = IdeStatus;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
StallExecutionProcessor(2);
|
||||
}
|
||||
|
||||
*Result = IdeStatus;
|
||||
return FALSE;
|
||||
return IdeStatus;
|
||||
}
|
||||
|
||||
static
|
||||
@@ -133,40 +88,14 @@ AtapSendCdb(
|
||||
_In_ PATA_DEVICE_REQUEST Request)
|
||||
{
|
||||
#if defined(ATA_SUPPORT_32_BIT_IO)
|
||||
if (AtapIs32BitIoSupported(DeviceUnit))
|
||||
{
|
||||
ATA_WRITE_BLOCK_32(DeviceUnit->Registers.Data,
|
||||
Request->Cdb,
|
||||
DeviceUnit->CdbSize / sizeof(USHORT));
|
||||
}
|
||||
else
|
||||
ATA_WRITE_BLOCK_32(DeviceUnit->Registers.Data,
|
||||
Request->Cdb,
|
||||
DeviceUnit->CdbSize / sizeof(USHORT));
|
||||
#else
|
||||
ATA_WRITE_BLOCK_16(DeviceUnit->Registers.Data,
|
||||
Request->Cdb,
|
||||
DeviceUnit->CdbSize);
|
||||
#endif
|
||||
{
|
||||
ATA_WRITE_BLOCK_16(DeviceUnit->Registers.Data,
|
||||
Request->Cdb,
|
||||
DeviceUnit->CdbSize);
|
||||
}
|
||||
|
||||
/*
|
||||
* In polled mode (interrupts disabled)
|
||||
* the NEC CDR-260 drive clears BSY before updating the interrupt reason register.
|
||||
* As a workaround, we will wait for the phase change.
|
||||
*/
|
||||
if (DeviceUnit->P.Flags & ATA_DEVICE_IS_NEC_CDR260)
|
||||
{
|
||||
ULONG i;
|
||||
|
||||
ATA_IO_WAIT();
|
||||
|
||||
for (i = 0; i < ATA_TIME_PHASE_CHANGE; ++i)
|
||||
{
|
||||
UCHAR InterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
|
||||
if (InterruptReason != ATAPI_INT_REASON_COD)
|
||||
break;
|
||||
|
||||
StallExecutionProcessor(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
@@ -178,7 +107,7 @@ AtapPioDataIn(
|
||||
ByteCount = min(ByteCount, DeviceUnit->BytesToTransfer);
|
||||
|
||||
#if defined(ATA_SUPPORT_32_BIT_IO)
|
||||
if (AtapIs32BitIoSupported(DeviceUnit))
|
||||
if (!(ByteCount & (sizeof(ULONG) - 1)))
|
||||
{
|
||||
ATA_READ_BLOCK_32(DeviceUnit->Registers.Data,
|
||||
(PULONG)DeviceUnit->DataBuffer,
|
||||
@@ -233,15 +162,37 @@ AtapSoftwareReset(
|
||||
StallExecutionProcessor(20);
|
||||
}
|
||||
|
||||
static
|
||||
VOID
|
||||
AtapDrainDeviceBuffer(
|
||||
_In_ PIDE_REGISTERS Registers)
|
||||
{
|
||||
UCHAR IdeStatus;
|
||||
ULONG i;
|
||||
|
||||
/* Try to clear the DRQ indication */
|
||||
for (i = 0; i < 0x10000 / sizeof(USHORT); ++i)
|
||||
{
|
||||
IdeStatus = ATA_READ(Registers->Status);
|
||||
if (!(IdeStatus & IDE_STATUS_DRQ))
|
||||
break;
|
||||
|
||||
READ_PORT_USHORT((PUSHORT)(ULONG_PTR)Registers->Data);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
BOOLEAN
|
||||
AtapPerformSoftwareReset(
|
||||
_In_ PHW_DEVICE_UNIT DeviceUnit)
|
||||
{
|
||||
PIDE_REGISTERS Registers = &DeviceUnit->Registers;
|
||||
UCHAR IdeStatus;
|
||||
|
||||
ERR("Reset device at %X:%u\n", Registers->Data, DeviceUnit->DeviceNumber);
|
||||
|
||||
AtapDrainDeviceBuffer(Registers);
|
||||
|
||||
/* Perform a software reset */
|
||||
AtapSoftwareReset(Registers);
|
||||
|
||||
@@ -253,7 +204,8 @@ AtapPerformSoftwareReset(
|
||||
}
|
||||
|
||||
/* Now wait for busy to clear */
|
||||
if (!AtapWaitForNotBusy(Registers, ATA_TIME_BUSY_RESET, NULL))
|
||||
IdeStatus = AtapWait(Registers, ATA_TIME_BUSY_RESET, IDE_STATUS_BUSY, 0);
|
||||
if (IdeStatus & IDE_STATUS_BUSY)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
@@ -272,6 +224,23 @@ AtapProcessAtapiRequest(
|
||||
InterruptReason &= ATAPI_INT_REASON_MASK;
|
||||
InterruptReason |= IdeStatus & IDE_STATUS_DRQ;
|
||||
|
||||
/*
|
||||
* The NEC CDR-C251 drive is not fully ATAPI-compliant
|
||||
* and clears BSY before raising the DRQ bit and updating the interrupt reason register.
|
||||
* As a workaround, we will wait a bit more in the case the valid IR is not quite there yet.
|
||||
*/
|
||||
if ((InterruptReason == ATAPI_INT_REASON_COD) || (InterruptReason == ATAPI_INT_REASON_IO))
|
||||
{
|
||||
IdeStatus = AtapWait(&DeviceUnit->Registers,
|
||||
ATA_TIME_DRQ_ASSERT,
|
||||
IDE_STATUS_DRQ,
|
||||
IDE_STATUS_DRQ);
|
||||
|
||||
InterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
|
||||
InterruptReason &= ATAPI_INT_REASON_MASK;
|
||||
InterruptReason |= IdeStatus & IDE_STATUS_DRQ;
|
||||
}
|
||||
|
||||
switch (InterruptReason)
|
||||
{
|
||||
case ATAPI_INT_REASON_AWAIT_CDB:
|
||||
@@ -341,8 +310,20 @@ AtapProcessAtaRequest(
|
||||
/* Read command */
|
||||
if (Request->DataBuffer)
|
||||
{
|
||||
/*
|
||||
* The NEC CDR-C251 drive clears BSY before raising the DRQ bit
|
||||
* while processing the ATAPI identify command.
|
||||
* Give the device a chance to assert that bit.
|
||||
*/
|
||||
if (!(IdeStatus & IDE_STATUS_DRQ))
|
||||
return ATA_STATUS_RESET;
|
||||
{
|
||||
IdeStatus = AtapWait(&DeviceUnit->Registers,
|
||||
ATA_TIME_DRQ_ASSERT,
|
||||
IDE_STATUS_DRQ,
|
||||
IDE_STATUS_DRQ);
|
||||
if (!(IdeStatus & IDE_STATUS_DRQ))
|
||||
return ATA_STATUS_RESET;
|
||||
}
|
||||
|
||||
/* Read the next data block */
|
||||
AtapPioDataIn(DeviceUnit, DeviceUnit->DrqByteCount);
|
||||
@@ -351,7 +332,11 @@ AtapProcessAtaRequest(
|
||||
return ATA_STATUS_PENDING;
|
||||
|
||||
/* All data has been transferred, wait for DRQ to clear */
|
||||
if (!AtapWaitForIdle(&DeviceUnit->Registers, &IdeStatus))
|
||||
IdeStatus = AtapWait(&DeviceUnit->Registers,
|
||||
ATA_TIME_DRQ_CLEAR,
|
||||
IDE_STATUS_BUSY | IDE_STATUS_DRQ,
|
||||
0);
|
||||
if (IdeStatus & (IDE_STATUS_BUSY | IDE_STATUS_DRQ))
|
||||
return ATA_STATUS_RESET;
|
||||
|
||||
if (IdeStatus & (IDE_STATUS_ERROR | IDE_STATUS_DEVICE_FAULT))
|
||||
@@ -363,16 +348,49 @@ AtapProcessAtaRequest(
|
||||
}
|
||||
|
||||
static
|
||||
BOOLEAN
|
||||
UCHAR
|
||||
AtapProcessRequest(
|
||||
_In_ PHW_DEVICE_UNIT DeviceUnit,
|
||||
_In_ PATA_DEVICE_REQUEST Request,
|
||||
_In_ UCHAR IdeStatus)
|
||||
{
|
||||
if (Request->Flags & REQUEST_FLAG_PACKET_COMMAND)
|
||||
return AtapProcessAtapiRequest(DeviceUnit, Request, IdeStatus);
|
||||
else
|
||||
UCHAR AtaStatus;
|
||||
|
||||
if (!(Request->Flags & REQUEST_FLAG_PACKET_COMMAND))
|
||||
return AtapProcessAtaRequest(DeviceUnit, Request, IdeStatus);
|
||||
|
||||
AtaStatus = AtapProcessAtapiRequest(DeviceUnit, Request, IdeStatus);
|
||||
|
||||
/*
|
||||
* In polled mode (interrupts disabled), the NEC CDR-260 drive behaves quite differently
|
||||
* that other devices. This drive does not raise BSY immediately
|
||||
* in response to a CDB or buffer write. The status and interrupt reason registers
|
||||
* remain invalid and unchanged for the time of media access.
|
||||
* As a workaround, we will wait for the phase change.
|
||||
*/
|
||||
if ((DeviceUnit->P.Flags & ATA_DEVICE_IS_NEC_CDR260) && (AtaStatus == ATA_STATUS_PENDING))
|
||||
{
|
||||
UCHAR OldInterruptReason, NewInterruptReason;
|
||||
ULONG i;
|
||||
|
||||
OldInterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
|
||||
|
||||
/* Set a long timeout on purpose */
|
||||
for (i = ATA_TIME_BUSY_POLL; i > 0; i--)
|
||||
{
|
||||
NewInterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
|
||||
if (NewInterruptReason != OldInterruptReason)
|
||||
break;
|
||||
|
||||
StallExecutionProcessor(10);
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
AtaStatus = ATA_STATUS_RESET;
|
||||
}
|
||||
}
|
||||
|
||||
return AtaStatus;
|
||||
}
|
||||
|
||||
static
|
||||
@@ -438,14 +456,15 @@ AtapSendCommand(
|
||||
_In_ PHW_DEVICE_UNIT DeviceUnit,
|
||||
_In_ PATA_DEVICE_REQUEST Request)
|
||||
{
|
||||
UCHAR AtaStatus;
|
||||
UCHAR IdeStatus, AtaStatus;
|
||||
|
||||
DeviceUnit->BytesToTransfer = Request->DataTransferLength;
|
||||
DeviceUnit->DataBuffer = Request->DataBuffer;
|
||||
|
||||
/* Select the device */
|
||||
AtapSelectDevice(&DeviceUnit->Registers, DeviceUnit->DeviceNumber);
|
||||
if (!AtapWaitForNotBusy(&DeviceUnit->Registers, ATA_TIME_BUSY_SELECT, NULL))
|
||||
IdeStatus = AtapWait(&DeviceUnit->Registers, ATA_TIME_BUSY_SELECT, IDE_STATUS_BUSY, 0);
|
||||
if (IdeStatus & IDE_STATUS_BUSY)
|
||||
return ATA_STATUS_RETRY;
|
||||
|
||||
/* Always disable interrupts, otherwise it may lead to problems in underlying BIOS firmware */
|
||||
@@ -458,11 +477,10 @@ AtapSendCommand(
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
UCHAR IdeStatus;
|
||||
|
||||
ATA_IO_WAIT();
|
||||
|
||||
if (!AtapWaitForNotBusy(&DeviceUnit->Registers, ATA_TIME_BUSY_POLL, &IdeStatus))
|
||||
IdeStatus = AtapWait(&DeviceUnit->Registers, ATA_TIME_BUSY_POLL, IDE_STATUS_BUSY, 0);
|
||||
if (IdeStatus & IDE_STATUS_BUSY)
|
||||
return ATA_STATUS_RESET;
|
||||
|
||||
AtaStatus = AtapProcessRequest(DeviceUnit, Request, IdeStatus);
|
||||
@@ -555,10 +573,6 @@ AtapIssueCommand(
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Turn off various things and retry the command */
|
||||
DeviceUnit->MultiSectorTransfer = 0;
|
||||
DeviceUnit->P.Flags &= ~ATA_DEVICE_FLAG_IO32;
|
||||
|
||||
if (!AtapPerformSoftwareReset(DeviceUnit))
|
||||
return FALSE;
|
||||
|
||||
@@ -738,7 +752,8 @@ AtapIsDevicePresent(
|
||||
if (ATA_READ(Registers->ByteCountHigh) != 0xAA)
|
||||
return FALSE;
|
||||
|
||||
if (!AtapWaitForNotBusy(Registers, ATA_TIME_BUSY_ENUM, &IdeStatus))
|
||||
IdeStatus = AtapWait(&DeviceUnit->Registers, ATA_TIME_BUSY_ENUM, IDE_STATUS_BUSY, 0);
|
||||
if (IdeStatus & (IDE_STATUS_BUSY | IDE_STATUS_DRQ))
|
||||
{
|
||||
ERR("Device %X:%u is busy %02x\n", Registers->Data, DeviceUnit->DeviceNumber, IdeStatus);
|
||||
|
||||
@@ -1090,7 +1105,7 @@ AtapAtaInitDevice(
|
||||
else
|
||||
{
|
||||
/* Using CHS addressing mode */
|
||||
TotalSectors = Cylinders * Heads * SectorsPerTrack;
|
||||
TotalSectors = (ULONG64)Cylinders * Heads * SectorsPerTrack;
|
||||
}
|
||||
|
||||
if (TotalSectors == 0)
|
||||
@@ -1221,6 +1236,7 @@ AtaReadLogicalSectors(
|
||||
PHW_DEVICE_UNIT Unit = (PHW_DEVICE_UNIT)DeviceUnit;
|
||||
ATA_DEVICE_REQUEST Request = { 0 };
|
||||
|
||||
ASSERT(Unit);
|
||||
ASSERT((SectorNumber + SectorCount) <= Unit->P.TotalSectors);
|
||||
ASSERT(SectorCount != 0);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* PROJECT: FreeLoader
|
||||
* LICENSE: MIT (https://spdx.org/licenses/MIT)
|
||||
* PURPOSE: Private header for ATA/ATAPI programmed I/O driver.
|
||||
* COPYRIGHT: Copyright 2019-2025 Dmitry Borisov (di.sean@protonmail.com)
|
||||
* COPYRIGHT: Copyright 2019-2026 Dmitry Borisov <di.sean@protonmail.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -28,6 +28,8 @@
|
||||
#if defined(SARCH_XBOX)
|
||||
/* It's safe to enable the multiple mode */
|
||||
#define ATA_ENABLE_MULTIPLE_MODE
|
||||
/* nVidia PCI IDE controllers have a 32-bit data port */
|
||||
#define ATA_SUPPORT_32_BIT_IO
|
||||
#endif
|
||||
|
||||
/* Delay of 400ns */
|
||||
@@ -69,9 +71,12 @@ typedef ULONG_PTR IDE_REG;
|
||||
#define ATA_TIME_BUSY_ENUM 100 ///< 1 ms
|
||||
#define ATA_TIME_BUSY_RESET 1000000 ///< 10 s
|
||||
#define ATA_TIME_RESET_SELECT 200000 ///< 2 s
|
||||
#define ATA_TIME_DRQ_CLEAR 100 ///< 200 us
|
||||
#define ATA_TIME_DRQ_CLEAR 1000 ///< 10 ms
|
||||
#define ATA_TIME_PHASE_CHANGE 100 ///< 1 ms
|
||||
|
||||
/* We keep the value as small as possible, since large timeout can break our error handling */
|
||||
#define ATA_TIME_DRQ_ASSERT 15 ///< 150 us
|
||||
|
||||
#define ATA_WRITE(Port, Value) \
|
||||
WRITE_PORT_UCHAR((PUCHAR)(ULONG_PTR)(Port), (Value))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* PROJECT: FreeLoader
|
||||
* LICENSE: MIT (https://spdx.org/licenses/MIT)
|
||||
* PURPOSE: ATA/ATAPI programmed I/O driver header file.
|
||||
* COPYRIGHT: Copyright 2019-2025 Dmitry Borisov (di.sean@protonmail.com)
|
||||
* COPYRIGHT: Copyright 2019-2026 Dmitry Borisov <di.sean@protonmail.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -31,7 +31,6 @@ typedef struct _DEVICE_UNIT
|
||||
#define ATA_DEVICE_LBA 0x00000002
|
||||
#define ATA_DEVICE_LBA48 0x00000004
|
||||
#define ATA_DEVICE_IS_NEC_CDR260 0x00000008
|
||||
#define ATA_DEVICE_FLAG_IO32 0x00000010
|
||||
} DEVICE_UNIT, *PDEVICE_UNIT;
|
||||
|
||||
/* FUNCTIONS ******************************************************************/
|
||||
|
||||
Reference in New Issue
Block a user