reactos/sdk/lib/drivers/sptilib/sptilib.c
Dmitry Borisov b558596409
[SPTILIB] Introduce SPTI static library for storage drivers (#8209)
Add a SCSI and ATA passthrough support helper library for direct use
from low-level storage drivers.

Tested with: CDRoller, CloneCD, Magic ISO
NOTE: Vbox seems to lack support for CD/DVD burning; tested on real hardware.

CORE-10191 CORE-16452
CORE-14788 CORE-18241
CORE-17256 CORE-13866
2025-07-13 22:49:51 +02:00

1008 lines
30 KiB
C

/*
* PROJECT: ReactOS Storage Stack
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: ATA and SCSI Pass Through Interface for storage drivers
* COPYRIGHT: Copyright 2025 Dmitry Borisov <di.sean@protonmail.com>
*/
/*
* This library provides helper code for handling ATA and SCSI Pass Through IOCTLs.
* Typically, these IRPs come from user-mode applications.
* The handler will translate the IOCTL into an SRB and send it to the PDO.
*/
/* INCLUDES *******************************************************************/
#include <ntddk.h>
#include <ntintsafe.h>
#include <pseh/pseh2.h>
#include <scsi.h>
#include <ntddscsi.h>
#define NDEBUG
#include <debug.h>
#include "sptilib.h"
#include "sptilibp.h"
/* PRIVATE FUNCTIONS **********************************************************/
_At_(IrpContext->Srb.SenseInfoBuffer, __drv_freesMem(Mem))
static
CODE_SEG("PAGE")
VOID
SptiFreeIrpContext(
_In_opt_ __drv_freesMem(Mem) PPASSTHROUGH_IRP_CONTEXT IrpContext)
{
PIRP Irp;
PAGED_CODE();
ASSERT(IrpContext);
Irp = IrpContext->Irp;
if (Irp)
{
if (Irp->MdlAddress)
{
MmUnlockPages(Irp->MdlAddress);
IoFreeMdl(Irp->MdlAddress);
}
Irp->MdlAddress = NULL;
IoFreeIrp(Irp);
}
if (IrpContext->Srb.SenseInfoBuffer)
ExFreePoolWithTag(IrpContext->Srb.SenseInfoBuffer, TAG_SPTI);
ExFreePoolWithTag(IrpContext, TAG_SPTI);
}
static IO_COMPLETION_ROUTINE SptiCompletionRoutine;
static
NTSTATUS
NTAPI
SptiCompletionRoutine(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp,
_In_reads_opt_(_Inexpressible_("varies")) PVOID Context)
{
UNREFERENCED_PARAMETER(DeviceObject);
if (Irp->PendingReturned)
KeSetEvent(Context, IO_NO_INCREMENT, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
__drv_allocatesMem(Mem)
static
CODE_SEG("PAGE")
PPASSTHROUGH_IRP_CONTEXT
SptiCreateIrpContext(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP OriginalIrp,
_In_ PVOID DataBuffer,
_In_ ULONG DataBufferLength,
_In_ BOOLEAN IsDirectMemoryAccess,
_In_ BOOLEAN IsBufferReadAccess)
{
PPASSTHROUGH_IRP_CONTEXT IrpContext;
PIRP Irp;
PIO_STACK_LOCATION IoStack;
PAGED_CODE();
IrpContext = ExAllocatePoolZero(NonPagedPool, sizeof(*IrpContext), TAG_SPTI);
if (!IrpContext)
return NULL;
IrpContext->Irp = Irp = IoAllocateIrp(DeviceObject->StackSize, 0);
if (!Irp)
goto Cleanup;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
if (DataBuffer)
{
if (!IoAllocateMdl(DataBuffer, DataBufferLength, FALSE, FALSE, Irp))
goto Cleanup;
ASSERT(Irp->MdlAddress);
_SEH2_TRY
{
MmProbeAndLockPages(Irp->MdlAddress,
IsDirectMemoryAccess ? OriginalIrp->RequestorMode : KernelMode,
IsBufferReadAccess ? IoReadAccess : IoWriteAccess);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
}
IrpContext->Srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
IrpContext->Srb.Length = RTL_FIELD_SIZE(PASSTHROUGH_IRP_CONTEXT, Srb);
IrpContext->Srb.OriginalRequest = Irp;
IrpContext->Srb.DataBuffer = DataBuffer;
IrpContext->Srb.DataTransferLength = DataBufferLength;
IrpContext->Srb.SrbFlags = SRB_FLAGS_NO_QUEUE_FREEZE;
IoStack = IoGetNextIrpStackLocation(Irp);
IoStack->MinorFunction = IRP_MN_SCSI_CLASS;
IoStack->MajorFunction = IRP_MJ_SCSI;
IoStack->Parameters.Scsi.Srb = &IrpContext->Srb;
return IrpContext;
Cleanup:
DPRINT1("Failed to create IRP\n");
SptiFreeIrpContext(IrpContext);
return NULL;
}
static
CODE_SEG("PAGE")
VOID
SptiInitializeOutputBuffer(
_In_ PIRP Irp)
{
PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
ULONG OutputBufferLength = IoStack->Parameters.DeviceIoControl.OutputBufferLength;
ULONG InputBufferLength = IoStack->Parameters.DeviceIoControl.InputBufferLength;
PAGED_CODE();
if (OutputBufferLength > InputBufferLength)
{
ULONG_PTR BufferStart = (ULONG_PTR)Irp->AssociatedIrp.SystemBuffer + InputBufferLength;
RtlZeroMemory((PVOID)BufferStart, OutputBufferLength - InputBufferLength);
}
}
static
CODE_SEG("PAGE")
NTSTATUS
SptiCallDriver(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PPASSTHROUGH_IRP_CONTEXT IrpContext)
{
PIRP Irp = IrpContext->Irp;
KEVENT Event;
NTSTATUS Status;
PAGED_CODE();
SptiInitializeOutputBuffer(Irp);
// TODO: Send the IRP in an asynchronous way (do not block the user app thread)
KeInitializeEvent(&Event, NotificationEvent, FALSE);
IoSetCompletionRoutine(Irp,
SptiCompletionRoutine,
&Event,
TRUE,
TRUE,
TRUE);
Status = IoCallDriver(DeviceObject, Irp);
if (Status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
Status = Irp->IoStatus.Status;
}
return Status;
}
_At_(Srb->SenseInfoBuffer, __drv_allocatesMem(Mem))
static
CODE_SEG("PAGE")
NTSTATUS
SptiSrbAppendSenseBuffer(
_In_ PSCSI_REQUEST_BLOCK Srb,
_In_ ULONG BufferSize)
{
PAGED_CODE();
if (BufferSize == 0)
{
Srb->SrbFlags |= SRB_FLAGS_DISABLE_AUTOSENSE;
return STATUS_SUCCESS;
}
Srb->SenseInfoBuffer = ExAllocatePoolUninitialized(NonPagedPoolCacheAligned,
BufferSize,
TAG_SPTI);
if (!Srb->SenseInfoBuffer)
return STATUS_INSUFFICIENT_RESOURCES;
Srb->SenseInfoBufferLength = BufferSize;
return STATUS_SUCCESS;
}
static
CODE_SEG("PAGE")
VOID
SptiTranslateTaskFileToCdb(
_Out_ CDB* __restrict Cdb,
_In_ UCHAR* __restrict TaskFile,
_In_ USHORT AtaFlags)
{
UCHAR Protocol;
PAGED_CODE();
Cdb->ATA_PASSTHROUGH16.OperationCode = SCSIOP_ATA_PASSTHROUGH16;
Cdb->ATA_PASSTHROUGH16.Features7_0 = TaskFile[8 + 0];
Cdb->ATA_PASSTHROUGH16.SectorCount7_0 = TaskFile[8 + 1];
Cdb->ATA_PASSTHROUGH16.LbaLow7_0 = TaskFile[8 + 2];
Cdb->ATA_PASSTHROUGH16.LbaMid7_0 = TaskFile[8 + 3];
Cdb->ATA_PASSTHROUGH16.LbaHigh7_0 = TaskFile[8 + 4];
Cdb->ATA_PASSTHROUGH16.Device = TaskFile[8 + 5];
Cdb->ATA_PASSTHROUGH16.Command = TaskFile[8 + 6];
if (AtaFlags & ATA_FLAGS_48BIT_COMMAND)
{
Cdb->ATA_PASSTHROUGH16.Features15_8 = TaskFile[0];
Cdb->ATA_PASSTHROUGH16.SectorCount15_8 = TaskFile[1];
Cdb->ATA_PASSTHROUGH16.LbaLow15_8 = TaskFile[2];
Cdb->ATA_PASSTHROUGH16.LbaMid15_8 = TaskFile[3];
Cdb->ATA_PASSTHROUGH16.LbaHigh15_8 = TaskFile[4];
Cdb->ATA_PASSTHROUGH16.Extend = 1;
}
/* Enable the check condition to get ATA fields from the device */
Cdb->ATA_PASSTHROUGH16.CkCond = 1;
if (AtaFlags & (ATA_FLAGS_DATA_IN | ATA_FLAGS_DATA_OUT))
{
Cdb->ATA_PASSTHROUGH16.TLength = 3;
if (AtaFlags & ATA_FLAGS_DATA_IN)
Cdb->ATA_PASSTHROUGH16.TDir = 1;
}
if (AtaFlags & ATA_FLAGS_USE_DMA)
{
Protocol = ATA_PASSTHROUGH_PROTOCOL_DMA;
}
else
{
if (AtaFlags & (ATA_FLAGS_DATA_IN | ATA_FLAGS_DATA_OUT))
{
if (AtaFlags & ATA_FLAGS_DATA_IN)
Protocol = ATA_PASSTHROUGH_PROTOCOL_PIO_DATA_IN;
else
Protocol = ATA_PASSTHROUGH_PROTOCOL_PIO_DATA_OUT;
}
else
{
Protocol = ATA_PASSTHROUGH_PROTOCOL_NON_DATA;
}
}
Cdb->ATA_PASSTHROUGH16.Protocol = Protocol;
}
static
CODE_SEG("PAGE")
BOOLEAN
SptiTranslateAtaStatusReturnToTaskFile(
_In_ SCSI_REQUEST_BLOCK* __restrict Srb,
_In_ DESCRIPTOR_SENSE_DATA* __restrict SenseData,
_Out_ UCHAR* __restrict TaskFile)
{
BOOLEAN WasCheckCondition = FALSE;
PAGED_CODE();
if (!(Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID))
return WasCheckCondition;
if (SenseData->ErrorCode == SCSI_SENSE_ERRORCODE_DESCRIPTOR_CURRENT)
{
PSCSI_SENSE_DESCRIPTOR_ATA_STATUS_RETURN Descriptor;
Descriptor = (PSCSI_SENSE_DESCRIPTOR_ATA_STATUS_RETURN)&SenseData->DescriptorBuffer[0];
if (Descriptor->Header.DescriptorType == SCSI_SENSE_DESCRIPTOR_TYPE_ATA_STATUS_RETURN)
{
TaskFile[8 + 0] = Descriptor->Error;
TaskFile[8 + 1] = Descriptor->SectorCount7_0;
TaskFile[8 + 2] = Descriptor->LbaLow7_0;
TaskFile[8 + 3] = Descriptor->LbaMid7_0;
TaskFile[8 + 4] = Descriptor->LbaHigh7_0;
TaskFile[8 + 5] = Descriptor->Device;
TaskFile[8 + 6] = Descriptor->Status;
if (Descriptor->Extend)
{
TaskFile[1] = Descriptor->SectorCount15_8;
TaskFile[2] = Descriptor->LbaLow15_8;
TaskFile[3] = Descriptor->LbaMid15_8;
TaskFile[4] = Descriptor->LbaHigh15_8;
}
}
}
/*
* The check condition bit will cause the APT command to return an error upon completion.
* We should determine if there was a real error or not.
*/
if ((Srb->ScsiStatus == SCSISTAT_CHECK_CONDITION) &&
(SenseData->SenseKey == SCSI_SENSE_RECOVERED_ERROR) &&
(SenseData->AdditionalSenseCode == SCSI_ADSENSE_NO_SENSE) &&
(SenseData->AdditionalSenseCodeQualifier ==
SCSI_SENSEQ_ATA_PASS_THROUGH_INFORMATION_AVAILABLE))
{
WasCheckCondition = TRUE;
}
return WasCheckCondition;
}
static
CODE_SEG("PAGE")
NTSTATUS
SptiTranslateAptToSrb(
_Out_ SCSI_REQUEST_BLOCK* __restrict Srb,
_In_ ATA_PASS_THROUGH_EX* __restrict Apt,
_In_ PUCHAR TaskFile)
{
NTSTATUS Status;
PAGED_CODE();
Status = SptiSrbAppendSenseBuffer(Srb,
FIELD_OFFSET(DESCRIPTOR_SENSE_DATA, DescriptorBuffer) +
sizeof(SCSI_SENSE_DESCRIPTOR_ATA_STATUS_RETURN));
if (!NT_SUCCESS(Status))
return Status;
Srb->CdbLength = RTL_FIELD_SIZE(CDB, ATA_PASSTHROUGH16);
Srb->TimeOutValue = Apt->TimeOutValue;
Srb->PathId = Apt->PathId;
Srb->TargetId = Apt->TargetId;
Srb->Lun = Apt->Lun;
if (Apt->AtaFlags & ATA_FLAGS_DATA_IN)
Srb->SrbFlags |= SRB_FLAGS_DATA_IN;
if (Apt->AtaFlags & ATA_FLAGS_DATA_OUT)
Srb->SrbFlags |= SRB_FLAGS_DATA_OUT;
SptiTranslateTaskFileToCdb((PCDB)Srb->Cdb, TaskFile, Apt->AtaFlags);
return STATUS_SUCCESS;
}
static
CODE_SEG("PAGE")
NTSTATUS
SptiInitializeApt(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp,
_In_ PIO_STACK_LOCATION IoStack,
_In_ BOOLEAN IsDirectMemoryAccess,
_In_ ULONG MaximumTransferLength,
_In_ ULONG MaximumPhysicalPages,
_In_ PATA_PASS_THROUGH_EX Apt,
_Out_ PPASSTHROUGH_DATA AptData,
_Out_ PUCHAR* TaskFile,
_Out_ PVOID* DataBuffer)
{
ULONG StructSize;
BOOLEAN IsNativeStructSize;
PAGED_CODE();
#if defined(_WIN64) && defined(BUILD_WOW64_ENABLED)
if (IoIs32bitProcess(Irp))
{
StructSize = sizeof(ATA_PASS_THROUGH_EX32);
*TaskFile = (PVOID)((ULONG_PTR)Apt + FIELD_OFFSET(ATA_PASS_THROUGH_EX32, PreviousTaskFile));
IsNativeStructSize = FALSE;
}
else
#endif
{
StructSize = sizeof(ATA_PASS_THROUGH_EX);
*TaskFile = (PVOID)((ULONG_PTR)Apt + FIELD_OFFSET(ATA_PASS_THROUGH_EX, PreviousTaskFile));
IsNativeStructSize = TRUE;
}
if (IoStack->Parameters.DeviceIoControl.InputBufferLength < StructSize)
{
DPRINT1("Buffer too small %lu/%lu\n",
IoStack->Parameters.DeviceIoControl.InputBufferLength, StructSize);
return STATUS_BUFFER_TOO_SMALL;
}
if (Apt->Length != StructSize)
{
DPRINT1("Unknown structure size %lu\n", StructSize);
return STATUS_REVISION_MISMATCH;
}
if (Apt->DataTransferLength > MaximumTransferLength)
{
DPRINT1("Too large transfer %lu/%lu\n", Apt->DataTransferLength, MaximumTransferLength);
return STATUS_INVALID_PARAMETER;
}
if (BYTES_TO_PAGES(Apt->DataTransferLength) > MaximumPhysicalPages)
{
DPRINT1("Too large transfer %lu/%lu pages\n",
BYTES_TO_PAGES(Apt->DataTransferLength), MaximumPhysicalPages);
return STATUS_INVALID_PARAMETER;
}
/* Retrieve the data buffer or the data buffer offset */
#if defined(_WIN64) && defined(BUILD_WOW64_ENABLED)
AptData->Buffer = NULL;
RtlCopyMemory(AptData,
(PVOID)((ULONG_PTR)Apt + FIELD_OFFSET(ATA_PASS_THROUGH_EX, DataBufferOffset)),
IsNativeStructSize ? sizeof(PVOID) : sizeof(ULONG32));
#else
DBG_UNREFERENCED_LOCAL_VARIABLE(IsNativeStructSize);
AptData->BufferOffset = Apt->DataBufferOffset;
#endif
if (Apt->DataTransferLength != 0)
{
if (IsDirectMemoryAccess)
*DataBuffer = AptData->Buffer;
else
*DataBuffer = (PVOID)((ULONG_PTR)Apt + AptData->BufferOffset);
}
else
{
*DataBuffer = NULL;
}
// FIXME: Validate the command opcode
if (*(PULONG_PTR)DataBuffer & DeviceObject->AlignmentRequirement)
{
DPRINT1("Unaligned data buffer %p:%lx\n",
(PVOID)*(PULONG_PTR)DataBuffer, DeviceObject->AlignmentRequirement);
return STATUS_INVALID_PARAMETER;
}
if (Apt->DataTransferLength & DeviceObject->AlignmentRequirement)
{
DPRINT1("Unaligned data transfer length %lx:%lx\n",
Apt->DataTransferLength, DeviceObject->AlignmentRequirement);
return STATUS_INVALID_PARAMETER;
}
if ((Apt->AtaFlags & (ATA_FLAGS_DATA_IN | ATA_FLAGS_DATA_OUT)) &&
(Apt->DataTransferLength == 0))
{
DPRINT1("Data buffer too small\n");
return STATUS_BUFFER_TOO_SMALL;
}
if (Apt->TimeOutValue < PASSTHROUGH_CMD_TIMEOUT_MIN_SEC ||
Apt->TimeOutValue > PASSTHROUGH_CMD_TIMEOUT_MAX_SEC)
{
DPRINT1("Invalid timeout value %lu\n", Apt->TimeOutValue);
return STATUS_INVALID_PARAMETER;
}
if (!IsDirectMemoryAccess)
{
ULONG_PTR DataBufferEnd;
NTSTATUS Status;
Status = RtlULongPtrAdd(AptData->BufferOffset, Apt->DataTransferLength, &DataBufferEnd);
if (!NT_SUCCESS(Status))
{
DPRINT1("Invalid data buffer offset\n");
return Status;
}
if ((Apt->Length > AptData->BufferOffset) && (Apt->DataTransferLength != 0))
{
DPRINT1("Data buffer overlaps APT\n");
return STATUS_INVALID_PARAMETER;
}
if ((Apt->AtaFlags & ATA_FLAGS_DATA_IN) &&
(DataBufferEnd > IoStack->Parameters.DeviceIoControl.OutputBufferLength))
{
DPRINT1("Data buffer outside of available space\n");
return STATUS_INVALID_PARAMETER;
}
if ((Apt->AtaFlags & ATA_FLAGS_DATA_OUT) &&
(DataBufferEnd > IoStack->Parameters.DeviceIoControl.InputBufferLength))
{
DPRINT1("Data buffer outside of available space\n");
return STATUS_INVALID_PARAMETER;
}
}
return STATUS_SUCCESS;
}
static
CODE_SEG("PAGE")
NTSTATUS
SptiTranslateSptToSrb(
_Out_ SCSI_REQUEST_BLOCK* __restrict Srb,
_In_ SCSI_PASS_THROUGH* __restrict Spt,
_In_ PUCHAR Cdb)
{
NTSTATUS Status;
PAGED_CODE();
Status = SptiSrbAppendSenseBuffer(Srb, Spt->SenseInfoLength);
if (!NT_SUCCESS(Status))
return Status;
Srb->CdbLength = Spt->CdbLength;
Srb->TimeOutValue = Spt->TimeOutValue;
Srb->PathId = Spt->PathId;
Srb->TargetId = Spt->TargetId;
Srb->Lun = Spt->Lun;
if (Spt->DataTransferLength != 0)
{
switch (Spt->DataIn)
{
case SCSI_IOCTL_DATA_IN:
Srb->SrbFlags |= SRB_FLAGS_DATA_IN;
break;
case SCSI_IOCTL_DATA_OUT:
Srb->SrbFlags |= SRB_FLAGS_DATA_OUT;
break;
default: // SCSI_IOCTL_DATA_UNSPECIFIED
Srb->SrbFlags |= SRB_FLAGS_UNSPECIFIED_DIRECTION;
break;
}
}
RtlCopyMemory(Srb->Cdb, Cdb, Srb->CdbLength);
return STATUS_SUCCESS;
}
static
CODE_SEG("PAGE")
NTSTATUS
SptiInitializeSpt(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp,
_In_ PIO_STACK_LOCATION IoStack,
_In_ BOOLEAN IsDirectMemoryAccess,
_In_ ULONG MaximumTransferLength,
_In_ ULONG MaximumPhysicalPages,
_In_ PSCSI_PASS_THROUGH Spt,
_Out_ PPASSTHROUGH_DATA SptData,
_Out_ PULONG SenseInfoOffset,
_Out_ PUCHAR* Cdb,
_Out_ PVOID* DataBuffer)
{
ULONG StructSize;
BOOLEAN IsNativeStructSize;
PULONG SenseInfoOffsetPtr;
PAGED_CODE();
#if defined(_WIN64) && defined(BUILD_WOW64_ENABLED)
if (IoIs32bitProcess(Irp))
{
StructSize = sizeof(SCSI_PASS_THROUGH32);
SenseInfoOffsetPtr = (PVOID)((ULONG_PTR)Spt +
FIELD_OFFSET(SCSI_PASS_THROUGH32, SenseInfoOffset));
*Cdb = (PVOID)((ULONG_PTR)Spt + FIELD_OFFSET(SCSI_PASS_THROUGH32, Cdb));
IsNativeStructSize = FALSE;
}
else
#endif
{
StructSize = sizeof(SCSI_PASS_THROUGH);
SenseInfoOffsetPtr = (PVOID)((ULONG_PTR)Spt +
FIELD_OFFSET(SCSI_PASS_THROUGH, SenseInfoOffset));
*Cdb = (PVOID)((ULONG_PTR)Spt + FIELD_OFFSET(SCSI_PASS_THROUGH, Cdb));
IsNativeStructSize = TRUE;
}
if (IoStack->Parameters.DeviceIoControl.InputBufferLength < StructSize)
{
DPRINT1("Buffer too small %lu/%lu\n",
IoStack->Parameters.DeviceIoControl.InputBufferLength, StructSize);
return STATUS_BUFFER_TOO_SMALL;
}
if (Spt->Length != StructSize)
{
DPRINT1("Unknown structure size %lu\n", StructSize);
return STATUS_REVISION_MISMATCH;
}
if (Spt->DataTransferLength > MaximumTransferLength)
{
DPRINT1("Too large transfer %lu/%lu\n", Spt->DataTransferLength, MaximumTransferLength);
return STATUS_INVALID_PARAMETER;
}
if (BYTES_TO_PAGES(Spt->DataTransferLength) > MaximumPhysicalPages)
{
DPRINT1("Too large transfer %lu/%lu pages\n",
BYTES_TO_PAGES(Spt->DataTransferLength), MaximumPhysicalPages);
return STATUS_INVALID_PARAMETER;
}
/* Retrieve the data buffer or the data buffer offset */
#if defined(_WIN64) && defined(BUILD_WOW64_ENABLED)
SptData->Buffer = NULL;
RtlCopyMemory(SptData,
(PVOID)((ULONG_PTR)Spt + FIELD_OFFSET(SCSI_PASS_THROUGH, DataBufferOffset)),
IsNativeStructSize ? sizeof(PVOID) : sizeof(ULONG32));
#else
DBG_UNREFERENCED_LOCAL_VARIABLE(IsNativeStructSize);
SptData->BufferOffset = Spt->DataBufferOffset;
#endif
*SenseInfoOffset = *SenseInfoOffsetPtr;
if (Spt->DataTransferLength != 0)
{
if (IsDirectMemoryAccess)
*DataBuffer = SptData->Buffer;
else
*DataBuffer = (PVOID)((ULONG_PTR)Spt + SptData->BufferOffset);
}
else
{
*DataBuffer = NULL;
}
// FIXME: Validate the command opcode
if (*(PULONG_PTR)DataBuffer & DeviceObject->AlignmentRequirement)
{
DPRINT1("Unaligned data buffer %p:%lx\n",
(PVOID)*(PULONG_PTR)DataBuffer, DeviceObject->AlignmentRequirement);
return STATUS_INVALID_PARAMETER;
}
if (Spt->DataTransferLength & DeviceObject->AlignmentRequirement)
{
DPRINT1("Unaligned data transfer length %lx:%lx\n",
Spt->DataTransferLength, DeviceObject->AlignmentRequirement);
return STATUS_INVALID_PARAMETER;
}
if (Spt->TimeOutValue < PASSTHROUGH_CMD_TIMEOUT_MIN_SEC ||
Spt->TimeOutValue > PASSTHROUGH_CMD_TIMEOUT_MAX_SEC)
{
DPRINT1("Invalid timeout value %lu\n", Spt->TimeOutValue);
return STATUS_INVALID_PARAMETER;
}
if (Spt->CdbLength > RTL_FIELD_SIZE(SCSI_REQUEST_BLOCK, Cdb))
{
DPRINT1("Invalid CDB size %u\n", Spt->CdbLength);
return STATUS_INVALID_PARAMETER;
}
if (Spt->DataIn > SCSI_IOCTL_DATA_UNSPECIFIED)
{
DPRINT1("Unknown DataIn value %u\n", Spt->DataIn);
return STATUS_INVALID_PARAMETER;
}
if (Spt->SenseInfoLength != 0)
{
ULONG SenseBufferEnd;
NTSTATUS Status;
Status = RtlULongAdd(*SenseInfoOffset, Spt->SenseInfoLength, &SenseBufferEnd);
if (!NT_SUCCESS(Status))
{
DPRINT1("Invalid sense buffer offset\n");
return Status;
}
if (Spt->Length > *SenseInfoOffset)
{
DPRINT1("Sense buffer overlaps SPT\n");
return STATUS_INVALID_PARAMETER;
}
if (!IsDirectMemoryAccess)
{
if ((SenseBufferEnd > SptData->BufferOffset) && (Spt->DataTransferLength != 0))
{
DPRINT1("Sense buffer overlaps data buffer\n");
return STATUS_INVALID_PARAMETER;
}
if (SenseBufferEnd > IoStack->Parameters.DeviceIoControl.OutputBufferLength)
{
DPRINT1("Sense buffer outside of available space\n");
return STATUS_INVALID_PARAMETER;
}
}
}
if (!IsDirectMemoryAccess)
{
ULONG_PTR DataBufferEnd;
NTSTATUS Status;
Status = RtlULongPtrAdd(SptData->BufferOffset, Spt->DataTransferLength, &DataBufferEnd);
if (!NT_SUCCESS(Status))
{
DPRINT1("Invalid data buffer offset\n");
return Status;
}
if ((Spt->Length > SptData->BufferOffset) && (Spt->DataTransferLength != 0))
{
DPRINT1("Data buffer inside of structure bounds\n");
return STATUS_INVALID_PARAMETER;
}
if (Spt->DataIn == SCSI_IOCTL_DATA_IN || Spt->DataIn == SCSI_IOCTL_DATA_UNSPECIFIED)
{
if (DataBufferEnd > IoStack->Parameters.DeviceIoControl.OutputBufferLength)
{
DPRINT1("Data buffer outside of available space\n");
return STATUS_INVALID_PARAMETER;
}
}
if (Spt->DataIn == SCSI_IOCTL_DATA_OUT || Spt->DataIn == SCSI_IOCTL_DATA_UNSPECIFIED)
{
if (DataBufferEnd > IoStack->Parameters.DeviceIoControl.InputBufferLength)
{
DPRINT1("Data buffer outside of available space\n");
return STATUS_INVALID_PARAMETER;
}
}
}
return STATUS_SUCCESS;
}
/* PUBLIC FUNCTIONS ***********************************************************/
CODE_SEG("PAGE")
NTSTATUS
SptiHandleAtaPassthru(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp,
_In_ ULONG MaximumTransferLength,
_In_ ULONG MaximumPhysicalPages)
{
PATA_PASS_THROUGH_EX Apt = Irp->AssociatedIrp.SystemBuffer;
PIO_STACK_LOCATION IoStack;
BOOLEAN IsDirectMemoryAccess;
PASSTHROUGH_DATA AptData;
PUCHAR TaskFile;
NTSTATUS Status;
PVOID DataBuffer;
PPASSTHROUGH_IRP_CONTEXT IrpContext;
PSCSI_REQUEST_BLOCK Srb;
PAGED_CODE();
IoStack = IoGetCurrentIrpStackLocation(Irp);
ASSERT(GET_IOCTL(IoStack) == IOCTL_ATA_PASS_THROUGH ||
GET_IOCTL(IoStack) == IOCTL_ATA_PASS_THROUGH_DIRECT);
IsDirectMemoryAccess = (GET_IOCTL(IoStack) == IOCTL_ATA_PASS_THROUGH_DIRECT);
Status = SptiInitializeApt(DeviceObject,
Irp,
IoStack,
IsDirectMemoryAccess,
MaximumTransferLength,
MaximumPhysicalPages,
Apt,
&AptData,
&TaskFile,
&DataBuffer);
if (!NT_SUCCESS(Status))
return Status;
DPRINT("APT: Flags %x DTL %lu\n", Apt->AtaFlags, Apt->DataTransferLength);
DPRINT("APT: Curr %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
TaskFile[8 + 0], TaskFile[8 + 1], TaskFile[8 + 2], TaskFile[8 + 3],
TaskFile[8 + 4], TaskFile[8 + 5], TaskFile[8 + 6],
Apt->AtaFlags,
Apt->DataTransferLength);
if (Apt->AtaFlags & ATA_FLAGS_48BIT_COMMAND)
{
DPRINT("APT: Prev %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
TaskFile[0], TaskFile[1], TaskFile[2], TaskFile[3],
TaskFile[4], TaskFile[5], TaskFile[6]);
}
IrpContext = SptiCreateIrpContext(DeviceObject,
Irp,
DataBuffer,
Apt->DataTransferLength,
IsDirectMemoryAccess,
!!(Apt->AtaFlags & ATA_FLAGS_DATA_OUT));
if (!IrpContext)
return STATUS_INSUFFICIENT_RESOURCES;
Status = SptiTranslateAptToSrb(&IrpContext->Srb, Apt, TaskFile);
if (!NT_SUCCESS(Status))
goto Cleanup;
Status = SptiCallDriver(DeviceObject, IrpContext);
Srb = &IrpContext->Srb;
if (SptiTranslateAtaStatusReturnToTaskFile(Srb, Srb->SenseInfoBuffer, TaskFile))
Status = STATUS_SUCCESS;
/* Update the output buffer size */
Apt->DataTransferLength = Srb->DataTransferLength;
if (IsDirectMemoryAccess)
{
Irp->IoStatus.Information = Apt->Length;
}
else
{
if ((Apt->AtaFlags & ATA_FLAGS_DATA_IN) && (AptData.BufferOffset != 0))
Irp->IoStatus.Information = AptData.BufferOffset + Apt->DataTransferLength;
else
Irp->IoStatus.Information = Apt->Length;
}
DPRINT("APT: Return %lx, DTL %lu, %lu bytes\n",
Status,
Apt->DataTransferLength,
(ULONG)Irp->IoStatus.Information);
DPRINT("APT: Return curr %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
TaskFile[8 + 0], TaskFile[8 + 1], TaskFile[8 + 2], TaskFile[8 + 3],
TaskFile[8 + 4], TaskFile[8 + 5], TaskFile[8 + 6]);
if (Apt->AtaFlags & ATA_FLAGS_48BIT_COMMAND)
{
DPRINT("APT: Return prev %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
TaskFile[0], TaskFile[1], TaskFile[2], TaskFile[3],
TaskFile[4], TaskFile[5], TaskFile[6]);
}
Cleanup:
SptiFreeIrpContext(IrpContext);
return Status;
}
CODE_SEG("PAGE")
NTSTATUS
SptiHandleScsiPassthru(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp,
_In_ ULONG MaximumTransferLength,
_In_ ULONG MaximumPhysicalPages)
{
PSCSI_PASS_THROUGH Spt = Irp->AssociatedIrp.SystemBuffer;
PIO_STACK_LOCATION IoStack;
BOOLEAN IsDirectMemoryAccess;
PASSTHROUGH_DATA SptData;
ULONG SenseInfoOffset;
PUCHAR Cdb;
NTSTATUS Status;
PVOID DataBuffer;
PPASSTHROUGH_IRP_CONTEXT IrpContext;
PSCSI_REQUEST_BLOCK Srb;
PAGED_CODE();
IoStack = IoGetCurrentIrpStackLocation(Irp);
ASSERT(GET_IOCTL(IoStack) == IOCTL_SCSI_PASS_THROUGH ||
GET_IOCTL(IoStack) == IOCTL_SCSI_PASS_THROUGH_DIRECT);
IsDirectMemoryAccess = (GET_IOCTL(IoStack) == IOCTL_SCSI_PASS_THROUGH_DIRECT);
Status = SptiInitializeSpt(DeviceObject,
Irp,
IoStack,
IsDirectMemoryAccess,
MaximumTransferLength,
MaximumPhysicalPages,
Spt,
&SptData,
&SenseInfoOffset,
&Cdb,
&DataBuffer);
if (!NT_SUCCESS(Status))
return Status;
DPRINT("SPT: Flags %u, DTL %lu, sense len %u\n",
Spt->DataIn,
Spt->DataTransferLength,
Spt->SenseInfoLength);
DPRINT("SPT: CDB %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
Cdb[0], Cdb[1], Cdb[2], Cdb[3], Cdb[4], Cdb[5], Cdb[6], Cdb[7], Cdb[8], Cdb[9]);
IrpContext = SptiCreateIrpContext(DeviceObject,
Irp,
DataBuffer,
Spt->DataTransferLength,
IsDirectMemoryAccess,
!!(Spt->DataIn == SCSI_IOCTL_DATA_OUT));
if (!IrpContext)
return STATUS_INSUFFICIENT_RESOURCES;
Status = SptiTranslateSptToSrb(&IrpContext->Srb, Spt, Cdb);
if (!NT_SUCCESS(Status))
goto Cleanup;
Status = SptiCallDriver(DeviceObject, IrpContext);
Srb = &IrpContext->Srb;
/*
* Low-level storage drivers map SRB_STATUS_DATA_OVERRUN to STATUS_BUFFER_OVERFLOW.
* This SRB status is usually set on an underrun and means success in that case.
*/
if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_DATA_OVERRUN)
Status = STATUS_SUCCESS;
if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID)
{
ASSERT(Srb->SenseInfoBufferLength <= Spt->SenseInfoLength);
Spt->SenseInfoLength = Srb->SenseInfoBufferLength;
/* Copy the sense buffer back */
RtlCopyMemory((PVOID)((ULONG_PTR)Spt + SenseInfoOffset),
Srb->SenseInfoBuffer,
Srb->SenseInfoBufferLength);
}
else
{
Spt->SenseInfoLength = 0;
}
Spt->ScsiStatus = Srb->ScsiStatus;
/* Update the output buffer size */
Spt->DataTransferLength = Srb->DataTransferLength;
if (IsDirectMemoryAccess)
{
if (Spt->SenseInfoLength != 0)
Irp->IoStatus.Information = SenseInfoOffset + Spt->SenseInfoLength;
else
Irp->IoStatus.Information = Spt->Length;
}
else
{
if ((Srb->SrbFlags & SRB_FLAGS_DATA_IN) && (SptData.BufferOffset != 0))
Irp->IoStatus.Information = SptData.BufferOffset + Spt->DataTransferLength;
else if (Spt->SenseInfoLength != 0)
Irp->IoStatus.Information = SenseInfoOffset + Spt->SenseInfoLength;
else
Irp->IoStatus.Information = Spt->Length;
}
DPRINT("SPT: Return %lx, DTL %lu, "
"Srb status %02x, SCSI status %02x, sense len %u, %lu bytes\n",
Status,
Spt->DataTransferLength,
Srb->SrbStatus,
Spt->ScsiStatus,
Spt->SenseInfoLength,
(ULONG)Irp->IoStatus.Information);
Cleanup:
SptiFreeIrpContext(IrpContext);
return Status;
}