Skip to content

Commit 91f7e02

Browse files
committed
[FREELDR:HWIDE] Improve support for ATAPI devices
- 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
1 parent 379eb14 commit 91f7e02

3 files changed

Lines changed: 130 additions & 109 deletions

File tree

boot/freeldr/freeldr/arch/drivers/hwide.c

Lines changed: 122 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* PROJECT: FreeLoader
33
* LICENSE: MIT (https://spdx.org/licenses/MIT)
44
* PURPOSE: ATA/ATAPI programmed I/O driver.
5-
* COPYRIGHT: Copyright 2019-2025 Dmitry Borisov (di.sean@protonmail.com)
5+
* COPYRIGHT: Copyright 2019-2026 Dmitry Borisov <di.sean@protonmail.com>
66
*/
77

88
/* INCLUDES *******************************************************************/
@@ -38,21 +38,6 @@ static PHW_DEVICE_UNIT AtapUnits[CHANNEL_MAX_CHANNELS * CHANNEL_MAX_DEVICES];
3838

3939
/* PRIVATE FUNCTIONS **********************************************************/
4040

41-
#if defined(ATA_SUPPORT_32_BIT_IO)
42-
static
43-
inline
44-
BOOLEAN
45-
AtapIs32BitIoSupported(
46-
_In_ PHW_DEVICE_UNIT DeviceUnit)
47-
{
48-
#if defined(ATA_ALWAYS_DO_32_BIT_IO)
49-
return TRUE;
50-
#else
51-
return !!(DeviceUnit->P.Flags & ATA_DEVICE_FLAG_IO32);
52-
#endif
53-
}
54-
#endif
55-
5641
static
5742
VOID
5843
AtapSelectDevice(
@@ -69,11 +54,12 @@ AtapSelectDevice(
6954
}
7055

7156
static
72-
BOOLEAN
73-
AtapWaitForNotBusy(
57+
UCHAR
58+
AtapWait(
7459
_In_ PIDE_REGISTERS Registers,
7560
_In_range_(>, 0) ULONG Timeout,
76-
_Out_opt_ PUCHAR Result)
61+
_In_ UCHAR Mask,
62+
_In_ UCHAR Value)
7763
{
7864
UCHAR IdeStatus;
7965
ULONG i;
@@ -83,47 +69,16 @@ AtapWaitForNotBusy(
8369
for (i = 0; i < Timeout; ++i)
8470
{
8571
IdeStatus = ATA_READ(Registers->Status);
86-
if (!(IdeStatus & IDE_STATUS_BUSY))
87-
{
88-
if (Result)
89-
*Result = IdeStatus;
90-
return TRUE;
91-
}
72+
if ((IdeStatus & Mask) == Value)
73+
break;
9274

9375
if (IdeStatus == 0xFF)
9476
break;
9577

9678
StallExecutionProcessor(10);
9779
}
9880

99-
if (Result)
100-
*Result = IdeStatus;
101-
return FALSE;
102-
}
103-
104-
static
105-
BOOLEAN
106-
AtapWaitForIdle(
107-
_In_ PIDE_REGISTERS Registers,
108-
_Out_ PUCHAR Result)
109-
{
110-
UCHAR IdeStatus;
111-
ULONG i;
112-
113-
for (i = 0; i < ATA_TIME_DRQ_CLEAR; ++i)
114-
{
115-
IdeStatus = ATA_READ(Registers->Status);
116-
if (!(IdeStatus & (IDE_STATUS_DRQ | IDE_STATUS_BUSY)))
117-
{
118-
*Result = IdeStatus;
119-
return TRUE;
120-
}
121-
122-
StallExecutionProcessor(2);
123-
}
124-
125-
*Result = IdeStatus;
126-
return FALSE;
81+
return IdeStatus;
12782
}
12883

12984
static
@@ -133,40 +88,14 @@ AtapSendCdb(
13388
_In_ PATA_DEVICE_REQUEST Request)
13489
{
13590
#if defined(ATA_SUPPORT_32_BIT_IO)
136-
if (AtapIs32BitIoSupported(DeviceUnit))
137-
{
138-
ATA_WRITE_BLOCK_32(DeviceUnit->Registers.Data,
139-
Request->Cdb,
140-
DeviceUnit->CdbSize / sizeof(USHORT));
141-
}
142-
else
91+
ATA_WRITE_BLOCK_32(DeviceUnit->Registers.Data,
92+
Request->Cdb,
93+
DeviceUnit->CdbSize / sizeof(USHORT));
94+
#else
95+
ATA_WRITE_BLOCK_16(DeviceUnit->Registers.Data,
96+
Request->Cdb,
97+
DeviceUnit->CdbSize);
14398
#endif
144-
{
145-
ATA_WRITE_BLOCK_16(DeviceUnit->Registers.Data,
146-
Request->Cdb,
147-
DeviceUnit->CdbSize);
148-
}
149-
150-
/*
151-
* In polled mode (interrupts disabled)
152-
* the NEC CDR-260 drive clears BSY before updating the interrupt reason register.
153-
* As a workaround, we will wait for the phase change.
154-
*/
155-
if (DeviceUnit->P.Flags & ATA_DEVICE_IS_NEC_CDR260)
156-
{
157-
ULONG i;
158-
159-
ATA_IO_WAIT();
160-
161-
for (i = 0; i < ATA_TIME_PHASE_CHANGE; ++i)
162-
{
163-
UCHAR InterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
164-
if (InterruptReason != ATAPI_INT_REASON_COD)
165-
break;
166-
167-
StallExecutionProcessor(10);
168-
}
169-
}
17099
}
171100

172101
static
@@ -178,7 +107,7 @@ AtapPioDataIn(
178107
ByteCount = min(ByteCount, DeviceUnit->BytesToTransfer);
179108

180109
#if defined(ATA_SUPPORT_32_BIT_IO)
181-
if (AtapIs32BitIoSupported(DeviceUnit))
110+
if (!(ByteCount & (sizeof(ULONG) - 1)))
182111
{
183112
ATA_READ_BLOCK_32(DeviceUnit->Registers.Data,
184113
(PULONG)DeviceUnit->DataBuffer,
@@ -233,15 +162,38 @@ AtapSoftwareReset(
233162
StallExecutionProcessor(20);
234163
}
235164

165+
static
166+
VOID
167+
AtapDrainDeviceBuffer(
168+
_In_ PIDE_REGISTERS Registers)
169+
{
170+
UCHAR IdeStatus;
171+
ULONG i;
172+
DECLSPEC_ALIGN(2) UCHAR Buffer[2];
173+
174+
/* Try to clear the DRQ indication */
175+
for (i = 0; i < 0x10000 / sizeof(USHORT); ++i)
176+
{
177+
IdeStatus = ATA_READ(Registers->Status);
178+
if (!(IdeStatus & IDE_STATUS_DRQ))
179+
break;
180+
181+
ATA_READ_BLOCK_16(DeviceUnit->Registers.Data, (PUSHORT)Buffer, 1);
182+
}
183+
}
184+
236185
static
237186
BOOLEAN
238187
AtapPerformSoftwareReset(
239188
_In_ PHW_DEVICE_UNIT DeviceUnit)
240189
{
241190
PIDE_REGISTERS Registers = &DeviceUnit->Registers;
191+
UCHAR IdeStatus;
242192

243193
ERR("Reset device at %X:%u\n", Registers->Data, DeviceUnit->DeviceNumber);
244194

195+
AtapDrainDeviceBuffer(Registers);
196+
245197
/* Perform a software reset */
246198
AtapSoftwareReset(Registers);
247199

@@ -253,7 +205,8 @@ AtapPerformSoftwareReset(
253205
}
254206

255207
/* Now wait for busy to clear */
256-
if (!AtapWaitForNotBusy(Registers, ATA_TIME_BUSY_RESET, NULL))
208+
IdeStatus = AtapWait(Registers, ATA_TIME_BUSY_RESET, IDE_STATUS_BUSY, 0);
209+
if (IdeStatus & IDE_STATUS_BUSY)
257210
return FALSE;
258211

259212
return TRUE;
@@ -272,6 +225,23 @@ AtapProcessAtapiRequest(
272225
InterruptReason &= ATAPI_INT_REASON_MASK;
273226
InterruptReason |= IdeStatus & IDE_STATUS_DRQ;
274227

228+
/*
229+
* The NEC CDR-C251 drive is not fully ATAPI-compliant
230+
* and clears BSY before raising the DRQ bit and updating the interrupt reason register.
231+
* As a workaround, we will wait a bit more in the case the valid IR is not quite there yet.
232+
*/
233+
if ((InterruptReason == ATAPI_INT_REASON_COD) || (InterruptReason == ATAPI_INT_REASON_IO))
234+
{
235+
IdeStatus = AtapWait(&DeviceUnit->Registers,
236+
ATA_TIME_DRQ_ASSERT,
237+
IDE_STATUS_DRQ,
238+
IDE_STATUS_DRQ);
239+
240+
InterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
241+
InterruptReason &= ATAPI_INT_REASON_MASK;
242+
InterruptReason |= IdeStatus & IDE_STATUS_DRQ;
243+
}
244+
275245
switch (InterruptReason)
276246
{
277247
case ATAPI_INT_REASON_AWAIT_CDB:
@@ -341,8 +311,20 @@ AtapProcessAtaRequest(
341311
/* Read command */
342312
if (Request->DataBuffer)
343313
{
314+
/*
315+
* The NEC CDR-C251 drive clears BSY before raising the DRQ bit
316+
* while processing the ATAPI identify command.
317+
* Give the device a chance to assert that bit.
318+
*/
344319
if (!(IdeStatus & IDE_STATUS_DRQ))
345-
return ATA_STATUS_RESET;
320+
{
321+
IdeStatus = AtapWait(&DeviceUnit->Registers,
322+
ATA_TIME_DRQ_ASSERT,
323+
IDE_STATUS_DRQ,
324+
IDE_STATUS_DRQ);
325+
if (!(IdeStatus & IDE_STATUS_DRQ))
326+
return ATA_STATUS_RESET;
327+
}
346328

347329
/* Read the next data block */
348330
AtapPioDataIn(DeviceUnit, DeviceUnit->DrqByteCount);
@@ -351,7 +333,11 @@ AtapProcessAtaRequest(
351333
return ATA_STATUS_PENDING;
352334

353335
/* All data has been transferred, wait for DRQ to clear */
354-
if (!AtapWaitForIdle(&DeviceUnit->Registers, &IdeStatus))
336+
IdeStatus = AtapWait(&DeviceUnit->Registers,
337+
ATA_TIME_DRQ_CLEAR,
338+
IDE_STATUS_BUSY | IDE_STATUS_DRQ,
339+
0);
340+
if (IdeStatus & (IDE_STATUS_BUSY | IDE_STATUS_DRQ))
355341
return ATA_STATUS_RESET;
356342

357343
if (IdeStatus & (IDE_STATUS_ERROR | IDE_STATUS_DEVICE_FAULT))
@@ -363,16 +349,49 @@ AtapProcessAtaRequest(
363349
}
364350

365351
static
366-
BOOLEAN
352+
UCHAR
367353
AtapProcessRequest(
368354
_In_ PHW_DEVICE_UNIT DeviceUnit,
369355
_In_ PATA_DEVICE_REQUEST Request,
370356
_In_ UCHAR IdeStatus)
371357
{
372-
if (Request->Flags & REQUEST_FLAG_PACKET_COMMAND)
373-
return AtapProcessAtapiRequest(DeviceUnit, Request, IdeStatus);
374-
else
358+
UCHAR AtaStatus;
359+
360+
if (!(Request->Flags & REQUEST_FLAG_PACKET_COMMAND))
375361
return AtapProcessAtaRequest(DeviceUnit, Request, IdeStatus);
362+
363+
AtaStatus = AtapProcessAtapiRequest(DeviceUnit, Request, IdeStatus);
364+
365+
/*
366+
* In polled mode (interrupts disabled), the NEC CDR-260 drive behaves quite differently
367+
* that other devices. This drive does not raise BSY immediately
368+
* in response to a CDB or buffer write. The status and interrupt reason registers
369+
* remain invalid and unchanged for the time of media access.
370+
* As a workaround, we will wait for the phase change.
371+
*/
372+
if ((DeviceUnit->P.Flags & ATA_DEVICE_IS_NEC_CDR260) && (AtaStatus == ATA_STATUS_PENDING))
373+
{
374+
UCHAR OldInterruptReason, NewInterruptReason;
375+
ULONG i;
376+
377+
OldInterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
378+
379+
/* Set a long timeout on purpose */
380+
for (i = ATA_TIME_BUSY_POLL; i > 0; i--)
381+
{
382+
NewInterruptReason = ATA_READ(DeviceUnit->Registers.InterruptReason);
383+
if (NewInterruptReason != OldInterruptReason)
384+
break;
385+
386+
StallExecutionProcessor(10);
387+
}
388+
if (i == 0)
389+
{
390+
AtaStatus = ATA_STATUS_RESET;
391+
}
392+
}
393+
394+
return AtaStatus;
376395
}
377396

378397
static
@@ -438,14 +457,15 @@ AtapSendCommand(
438457
_In_ PHW_DEVICE_UNIT DeviceUnit,
439458
_In_ PATA_DEVICE_REQUEST Request)
440459
{
441-
UCHAR AtaStatus;
460+
UCHAR IdeStatus, AtaStatus;
442461

443462
DeviceUnit->BytesToTransfer = Request->DataTransferLength;
444463
DeviceUnit->DataBuffer = Request->DataBuffer;
445464

446465
/* Select the device */
447466
AtapSelectDevice(&DeviceUnit->Registers, DeviceUnit->DeviceNumber);
448-
if (!AtapWaitForNotBusy(&DeviceUnit->Registers, ATA_TIME_BUSY_SELECT, NULL))
467+
IdeStatus = AtapWait(&DeviceUnit->Registers, ATA_TIME_BUSY_SELECT, IDE_STATUS_BUSY, 0);
468+
if (IdeStatus & IDE_STATUS_BUSY)
449469
return ATA_STATUS_RETRY;
450470

451471
/* Always disable interrupts, otherwise it may lead to problems in underlying BIOS firmware */
@@ -458,11 +478,10 @@ AtapSendCommand(
458478

459479
while (TRUE)
460480
{
461-
UCHAR IdeStatus;
462-
463481
ATA_IO_WAIT();
464482

465-
if (!AtapWaitForNotBusy(&DeviceUnit->Registers, ATA_TIME_BUSY_POLL, &IdeStatus))
483+
IdeStatus = AtapWait(&DeviceUnit->Registers, ATA_TIME_BUSY_POLL, IDE_STATUS_BUSY, 0);
484+
if (IdeStatus & IDE_STATUS_BUSY)
466485
return ATA_STATUS_RESET;
467486

468487
AtaStatus = AtapProcessRequest(DeviceUnit, Request, IdeStatus);
@@ -555,10 +574,6 @@ AtapIssueCommand(
555574
return FALSE;
556575
}
557576

558-
/* Turn off various things and retry the command */
559-
DeviceUnit->MultiSectorTransfer = 0;
560-
DeviceUnit->P.Flags &= ~ATA_DEVICE_FLAG_IO32;
561-
562577
if (!AtapPerformSoftwareReset(DeviceUnit))
563578
return FALSE;
564579

@@ -738,7 +753,8 @@ AtapIsDevicePresent(
738753
if (ATA_READ(Registers->ByteCountHigh) != 0xAA)
739754
return FALSE;
740755

741-
if (!AtapWaitForNotBusy(Registers, ATA_TIME_BUSY_ENUM, &IdeStatus))
756+
IdeStatus = AtapWait(&DeviceUnit->Registers, ATA_TIME_BUSY_ENUM, IDE_STATUS_BUSY, 0);
757+
if (IdeStatus & (IDE_STATUS_BUSY | IDE_STATUS_DRQ))
742758
{
743759
ERR("Device %X:%u is busy %02x\n", Registers->Data, DeviceUnit->DeviceNumber, IdeStatus);
744760

@@ -1090,7 +1106,7 @@ AtapAtaInitDevice(
10901106
else
10911107
{
10921108
/* Using CHS addressing mode */
1093-
TotalSectors = Cylinders * Heads * SectorsPerTrack;
1109+
TotalSectors = UInt32x32To64(Cylinders, Heads) * SectorsPerTrack;
10941110
}
10951111

10961112
if (TotalSectors == 0)
@@ -1221,6 +1237,7 @@ AtaReadLogicalSectors(
12211237
PHW_DEVICE_UNIT Unit = (PHW_DEVICE_UNIT)DeviceUnit;
12221238
ATA_DEVICE_REQUEST Request = { 0 };
12231239

1240+
ASSERT(Unit);
12241241
ASSERT((SectorNumber + SectorCount) <= Unit->P.TotalSectors);
12251242
ASSERT(SectorCount != 0);
12261243

0 commit comments

Comments
 (0)