Files
reactos/drivers/storage/ide/atapi/dev_identify.c
Dmitry Borisov ac33647888 [ATAPI] Add ATA storage driver
CORE-17256
CORE-17191
CORE-17716
CORE-17977
CORE-13976
CORE-16216
2026-04-21 15:01:22 -05:00

368 lines
12 KiB
C

/*
* PROJECT: ReactOS ATA Port Driver
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Device identification
* COPYRIGHT: Copyright 2026 Dmitry Borisov <di.sean@protonmail.com>
*/
/* INCLUDES *******************************************************************/
#include "atapi.h"
/* FUNCTIONS ******************************************************************/
static
BOOLEAN
AtaDeviceIdentifyDataEqual(
_In_ PIDENTIFY_DEVICE_DATA IdentifyData1,
_In_ PIDENTIFY_DEVICE_DATA IdentifyData2)
{
if (!RtlEqualMemory(IdentifyData1->SerialNumber,
IdentifyData2->SerialNumber,
RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber)))
{
return FALSE;
}
if (!RtlEqualMemory(IdentifyData1->FirmwareRevision,
IdentifyData2->FirmwareRevision,
RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, FirmwareRevision)))
{
return FALSE;
}
if (!RtlEqualMemory(IdentifyData1->ModelNumber,
IdentifyData2->ModelNumber,
RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, ModelNumber)))
{
return FALSE;
}
return TRUE;
}
NTSTATUS
AtaDeviceSendIdentify(
_In_ PATAPORT_PORT_DATA PortData,
_In_ PATAPORT_DEVICE_EXTENSION DevExt,
_In_ UCHAR Command)
{
PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest;
TRACE("CH %lu: Send %s identify device %u\n",
PortData->PortNumber,
Command == IDE_COMMAND_IDENTIFY ? "ATA" : "ATAPI",
DevExt->Device.AtaScsiAddress.TargetId);
/*
* For PATA devices, disable interrupts for the identify command and use polling instead.
*
* On some single device 1 configurations or non-existent IDE channels
* the status register stuck permanently at a value of 0,
* and we incorrectly assume that the device is present.
* This will result in a taskfile timeout
* which must be avoided as it would cause hangs at boot time.
*/
Request->Flags = REQUEST_FLAG_POLL;
Request->Flags |= REQUEST_FLAG_DATA_IN | REQUEST_FLAG_HAS_LOCAL_BUFFER;
Request->TimeOut = 10;
Request->DataTransferLength = sizeof(DevExt->IdentifyDeviceData);
RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile));
Request->TaskFile.Command = Command;
return AtaPortSendRequest(PortData, DevExt);
}
static
NTSTATUS
AtaDeviceSpinUp(
_In_ PATAPORT_PORT_DATA PortData,
_In_ PATAPORT_DEVICE_EXTENSION DevExt)
{
PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest;
TRACE("CH %lu: Spin-up device %u\n",
PortData->PortNumber,
DevExt->Device.AtaScsiAddress.TargetId);
Request->Flags = 0;
Request->TimeOut = 20;
Request->TaskFile.Command = IDE_COMMAND_SET_FEATURE;
Request->TaskFile.Feature = IDE_FEATURE_PUIS_SPIN_UP;
return AtaPortSendRequest(PortData, DevExt);
}
static
BOOLEAN
AtaDeviceIsXboxDrive(
_In_ PATAPORT_DEVICE_EXTENSION DevExt)
{
static const PCSTR AtapBrokenInquiryDrive[] =
{
"THOMSON-DVD",
"PHILIPS XBOX DVD DRIVE",
"PHILIPS J5 3235C",
"SAMSUNG DVD-ROM SDG-605B"
// TODO: Xbox 360 drives are also affected.
};
CHAR ModelNumber[26];
PUCHAR End;
ULONG i;
End = AtaCopyIdStringUnsafe((PUCHAR)ModelNumber,
DevExt->IdentifyDeviceData.ModelNumber,
sizeof(ModelNumber));
*End = ANSI_NULL;
for (i = 0; i < RTL_NUMBER_OF(AtapBrokenInquiryDrive); ++i)
{
if (strcmp(ModelNumber, AtapBrokenInquiryDrive[i]) == 0)
return TRUE;
}
return FALSE;
}
static
VOID
AtaCreateAtapiStandardInquiryData(
_In_ PIDENTIFY_DEVICE_DATA IdentifyData,
_Out_ PINQUIRYDATA InquiryData)
{
InquiryData->DeviceType = READ_ONLY_DIRECT_ACCESS_DEVICE;
InquiryData->RemovableMedia = AtaDevIsRemovable(IdentifyData);
InquiryData->Versions = 0x04; // SPC-2
InquiryData->ResponseDataFormat = 2; // This means "Complies to this standard"
InquiryData->AdditionalLength =
INQUIRYDATABUFFERSIZE - RTL_SIZEOF_THROUGH_FIELD(INQUIRYDATA, AdditionalLength);
/* Copy ModelNumber from a byte-swapped ATA string */
AtaCopyIdStringSafe((PCHAR)InquiryData->VendorId,
IdentifyData->ModelNumber,
sizeof(IdentifyData->ModelNumber),
' ');
AtaSwapIdString(InquiryData->VendorId, sizeof(IdentifyData->ModelNumber) / 2);
/* Copy FirmwareRevision from a byte-swapped ATA string */
AtaCopyIdStringSafe((PCHAR)InquiryData->ProductRevisionLevel,
IdentifyData->FirmwareRevision,
sizeof(InquiryData->ProductRevisionLevel),
' ');
AtaSwapIdString(InquiryData->ProductRevisionLevel,
sizeof(InquiryData->ProductRevisionLevel) / 2);
}
static
NTSTATUS
AtaDeviceSendInquiry(
_In_ PATAPORT_PORT_DATA PortData,
_In_ PATAPORT_DEVICE_EXTENSION DevExt)
{
PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest;
NTSTATUS Status;
PCDB Cdb;
TRACE("CH %lx: Send inquiry device %u\n",
PortData->PortNumber,
DevExt->Device.AtaScsiAddress.TargetId);
Request->Flags = REQUEST_FLAG_DATA_IN |
REQUEST_FLAG_PACKET_COMMAND |
REQUEST_FLAG_HAS_LOCAL_BUFFER;
Request->TimeOut = 3;
Request->DataTransferLength = INQUIRYDATABUFFERSIZE;
RtlZeroMemory(Request->Cdb, sizeof(Request->Cdb));
Cdb = (PCDB)&Request->Cdb;
Cdb->CDB6INQUIRY.OperationCode = SCSIOP_INQUIRY;
Cdb->CDB6INQUIRY.LogicalUnitNumber = DevExt->Device.AtaScsiAddress.Lun;
Cdb->CDB6INQUIRY.AllocationLength = INQUIRYDATABUFFERSIZE;
Status = AtaPortSendRequest(PortData, DevExt);
if (Status == STATUS_ADAPTER_HARDWARE_ERROR)
return Status;
if (!NT_SUCCESS(Status))
{
if (DevExt->Device.AtaScsiAddress.Lun == 0 && AtaDeviceIsXboxDrive(DevExt))
{
DevExt->Device.DeviceFlags |= DEVICE_CACHE_INQUIRY;
/*
* The INQUIRY command is mandatory to be implemented by ATAPI devices,
* but some Xbox drives violate this.
*/
AtaCreateAtapiStandardInquiryData(&DevExt->IdentifyDeviceData,
DevExt->Device.LocalBuffer);
Status = DEV_STATUS_NEW_DEVICE;
}
else
{
Status = DEV_STATUS_NO_DEVICE;
}
}
return STATUS_SUCCESS;
}
ATA_DEVICE_STATUS
AtaPortIdentifyDevice(
_In_ PATAPORT_PORT_DATA PortData,
_In_ PATAPORT_DEVICE_EXTENSION DevExt)
{
PIDENTIFY_DEVICE_DATA IdentifyData = DevExt->Device.LocalBuffer;
ATA_CONNECTION_STATUS ConnectionStatus;
NTSTATUS Status;
BOOLEAN StartFromAtapi, IsSameDevice;
ULONG Attempt, RetryCount;
ATA_DEVICE_TYPE DeviceType;
_InterlockedOr(&PortData->InterruptFlags, PORT_INT_FLAG_IGNORE_LINK_IRQ);
ConnectionStatus = PortData->IdentifyDevice(PortData->ChannelContext,
DevExt->Device.AtaScsiAddress.TargetId);
_InterlockedAnd(&PortData->InterruptFlags, ~PORT_INT_FLAG_IGNORE_LINK_IRQ);
if (ConnectionStatus == CONN_STATUS_FAILURE)
return DEV_STATUS_FAILED;
if (ConnectionStatus == CONN_STATUS_NO_DEVICE)
return DEV_STATUS_NO_DEVICE;
/* Power up the device if needed */
Status = AtaPortCheckDevicePowerState(PortData, DevExt);
if (Status == STATUS_ADAPTER_HARDWARE_ERROR)
return DEV_STATUS_FAILED;
/*
* Try the known device type first.
* This may speed up device detection by eliminating I/O errors.
*/
if (DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED)
StartFromAtapi = (ConnectionStatus == CONN_STATUS_DEV_ATAPI);
else
StartFromAtapi = IS_ATAPI(&DevExt->Device);
/* Look for ATA/ATAPI devices */
for (RetryCount = 0; RetryCount < 2; ++RetryCount)
{
/* Send the identify command */
for (Attempt = 0; Attempt < 2; ++Attempt)
{
if (StartFromAtapi)
DeviceType = DEV_ATAPI;
else
DeviceType = DEV_ATA;
/* Swap device types */
if (Attempt != 0)
DeviceType ^= (DEV_ATAPI ^ DEV_ATA);
Status = AtaDeviceSendIdentify(PortData,
DevExt,
DeviceType == DEV_ATAPI ?
IDE_COMMAND_ATAPI_IDENTIFY : IDE_COMMAND_IDENTIFY);
if (Status == STATUS_ADAPTER_HARDWARE_ERROR)
return DEV_STATUS_FAILED;
if (NT_SUCCESS(Status))
break;
}
if (!NT_SUCCESS(Status))
return DEV_STATUS_NO_DEVICE;
/*
* The drive needs to be spun up
* or the device was unable to return complete identity data.
*/
if (AtaDevInPuisState(IdentifyData) || AtaDevIsIdentifyDataIncomplete(IdentifyData))
{
Status = AtaDeviceSpinUp(PortData, DevExt);
if (Status == STATUS_ADAPTER_HARDWARE_ERROR)
return DEV_STATUS_FAILED;
if (!NT_SUCCESS(Status))
ERR("Failed to spin-up device\n");
continue;
}
}
/* Verify the checksum */
if (!AtaDevIsIdentifyDataValid(IdentifyData))
{
ERR("Identify data CRC error\n");
return DEV_STATUS_FAILED;
}
if (DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED)
{
IsSameDevice = FALSE;
}
else
{
IsSameDevice = (DeviceType == DevExt->DeviceType) &&
(AtaDeviceIdentifyDataEqual(&DevExt->IdentifyDeviceData,
DevExt->Device.LocalBuffer));
}
/* Update identify data */
RtlCopyMemory(&DevExt->IdentifyDeviceData,
DevExt->Device.LocalBuffer,
sizeof(DevExt->IdentifyDeviceData));
DevExt->DeviceType = DeviceType;
if (IsSameDevice)
return DEV_STATUS_SAME_DEVICE;
DevExt->Device.TransportFlags &= ~(DEVICE_IS_ATAPI |
DEVICE_HAS_CDB_INTERRUPT |
DEVICE_NEED_DMA_DIRECTION |
DEVICE_IS_NEC_CDR260);
if (DeviceType == DEV_ATAPI)
{
DevExt->Device.TransportFlags |= DEVICE_IS_ATAPI;
DevExt->Device.CdbSize = AtaDevCdbSizeInWords(&DevExt->IdentifyPacketData);
TRACE("Device has CDB size of %u bytes\n", DevExt->Device.CdbSize * 2);
if (AtaDevHasCdbInterrupt(&DevExt->IdentifyPacketData))
{
TRACE("Device has CDB interrupt\n");
DevExt->Device.TransportFlags |= DEVICE_HAS_CDB_INTERRUPT;
}
if (AtaDevIsDmaDirectionRequired(&DevExt->IdentifyPacketData))
{
TRACE("Device needs DMA DIR\n");
DevExt->Device.TransportFlags |= DEVICE_NEED_DMA_DIRECTION;
}
/* The NEC CDR-260 string is not byteswapped */
if (DevExt->IdentifyPacketData.ModelNumber[0] == 'N' &&
DevExt->IdentifyPacketData.ModelNumber[1] == 'E' &&
DevExt->IdentifyPacketData.ModelNumber[2] == 'C')
{
TRACE("Device is a NEC CDR-260 drive\n");
DevExt->Device.TransportFlags |= DEVICE_IS_NEC_CDR260;
}
Status = AtaDeviceSendInquiry(PortData, DevExt);
if (Status == STATUS_ADAPTER_HARDWARE_ERROR)
return DEV_STATUS_FAILED;
/* Update the inquiry data */
RtlCopyMemory(&DevExt->InquiryData,
DevExt->Device.LocalBuffer,
sizeof(DevExt->InquiryData));
}
return DEV_STATUS_NEW_DEVICE;
}