diff --git a/boot/freeldr/freeldr/arch/drivers/hwide.c b/boot/freeldr/freeldr/arch/drivers/hwide.c index 2baffe48345..4d6f7d893e0 100644 --- a/boot/freeldr/freeldr/arch/drivers/hwide.c +++ b/boot/freeldr/freeldr/arch/drivers/hwide.c @@ -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 */ /* 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); diff --git a/boot/freeldr/freeldr/arch/drivers/hwidep.h b/boot/freeldr/freeldr/arch/drivers/hwidep.h index 748b58b2440..4a79bfcb1d1 100644 --- a/boot/freeldr/freeldr/arch/drivers/hwidep.h +++ b/boot/freeldr/freeldr/arch/drivers/hwidep.h @@ -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 */ #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)) diff --git a/boot/freeldr/freeldr/include/hwide.h b/boot/freeldr/freeldr/include/hwide.h index 60b2cce415c..ab5360c15aa 100644 --- a/boot/freeldr/freeldr/include/hwide.h +++ b/boot/freeldr/freeldr/include/hwide.h @@ -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 */ #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 ******************************************************************/