mirror of
https://github.com/reactos/reactos.git
synced 2024-08-27 21:58:49 +00:00
9168 lines
323 KiB
C
9168 lines
323 KiB
C
/*++
|
|
|
|
Copyright (C) Microsoft Corporation, 1991 - 2010
|
|
|
|
Module Name:
|
|
|
|
utils.c
|
|
|
|
Abstract:
|
|
|
|
SCSI class driver routines
|
|
|
|
Environment:
|
|
|
|
kernel mode only
|
|
|
|
Notes:
|
|
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "classp.h"
|
|
#include "debug.h"
|
|
#include <ntiologc.h>
|
|
|
|
|
|
#ifdef DEBUG_USE_WPP
|
|
#include "utils.tmh"
|
|
#endif
|
|
|
|
//
|
|
// Constant value used in firmware upgrade process.
|
|
//
|
|
#define FIRMWARE_ACTIVATE_TIMEOUT_VALUE 30
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, ClassGetDeviceParameter)
|
|
#pragma alloc_text(PAGE, ClassScanForSpecial)
|
|
#pragma alloc_text(PAGE, ClassSetDeviceParameter)
|
|
#pragma alloc_text(PAGE, ClasspMyStringMatches)
|
|
#pragma alloc_text(PAGE, ClasspDeviceCopyOffloadProperty)
|
|
#pragma alloc_text(PAGE, ClasspValidateOffloadSupported)
|
|
#pragma alloc_text(PAGE, ClasspValidateOffloadInputParameters)
|
|
#endif
|
|
|
|
// custom string match -- careful!
|
|
BOOLEAN ClasspMyStringMatches(_In_opt_z_ PCHAR StringToMatch, _In_z_ PCHAR TargetString)
|
|
{
|
|
ULONG length; // strlen returns an int, not size_t (!)
|
|
PAGED_CODE();
|
|
NT_ASSERT(TargetString);
|
|
// if no match requested, return TRUE
|
|
if (StringToMatch == NULL) {
|
|
return TRUE;
|
|
}
|
|
// cache the string length for efficiency
|
|
length = (ULONG)strlen(StringToMatch);
|
|
// ZERO-length strings may only match zero-length strings
|
|
if (length == 0) {
|
|
return (strlen(TargetString) == 0);
|
|
}
|
|
// strncmp returns zero if the strings match
|
|
return (strncmp(StringToMatch, TargetString, length) == 0);
|
|
}
|
|
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
VOID
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClassGetDeviceParameter(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_In_opt_ PWSTR SubkeyName,
|
|
_In_ PWSTR ParameterName,
|
|
_Inout_ PULONG ParameterValue // also default value
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0};
|
|
HANDLE deviceParameterHandle = NULL;
|
|
HANDLE deviceSubkeyHandle = NULL;
|
|
ULONG defaultParameterValue;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// open the given parameter
|
|
//
|
|
|
|
status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
|
|
PLUGPLAY_REGKEY_DEVICE,
|
|
KEY_READ,
|
|
&deviceParameterHandle);
|
|
|
|
if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
|
|
|
|
UNICODE_STRING subkeyName;
|
|
OBJECT_ATTRIBUTES objectAttributes = {0};
|
|
|
|
RtlInitUnicodeString(&subkeyName, SubkeyName);
|
|
InitializeObjectAttributes(&objectAttributes,
|
|
&subkeyName,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
deviceParameterHandle,
|
|
NULL);
|
|
|
|
status = ZwOpenKey(&deviceSubkeyHandle,
|
|
KEY_READ,
|
|
&objectAttributes);
|
|
if (!NT_SUCCESS(status)) {
|
|
ZwClose(deviceParameterHandle);
|
|
}
|
|
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
defaultParameterValue = *ParameterValue;
|
|
|
|
queryTable->Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_TYPECHECK;
|
|
queryTable->Name = ParameterName;
|
|
queryTable->EntryContext = ParameterValue;
|
|
queryTable->DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
|
|
queryTable->DefaultData = NULL;
|
|
queryTable->DefaultLength = 0;
|
|
|
|
status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)(SubkeyName ?
|
|
deviceSubkeyHandle :
|
|
deviceParameterHandle),
|
|
queryTable,
|
|
NULL,
|
|
NULL);
|
|
if (!NT_SUCCESS(status)) {
|
|
*ParameterValue = defaultParameterValue; // use default value
|
|
}
|
|
|
|
//
|
|
// close what we open
|
|
//
|
|
|
|
if (SubkeyName) {
|
|
ZwClose(deviceSubkeyHandle);
|
|
}
|
|
|
|
ZwClose(deviceParameterHandle);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// Windows 2000 SP3 uses the driver-specific key, so look in there
|
|
//
|
|
|
|
status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
|
|
PLUGPLAY_REGKEY_DRIVER,
|
|
KEY_READ,
|
|
&deviceParameterHandle);
|
|
|
|
if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
|
|
|
|
UNICODE_STRING subkeyName;
|
|
OBJECT_ATTRIBUTES objectAttributes = {0};
|
|
|
|
RtlInitUnicodeString(&subkeyName, SubkeyName);
|
|
InitializeObjectAttributes(&objectAttributes,
|
|
&subkeyName,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
deviceParameterHandle,
|
|
NULL);
|
|
|
|
status = ZwOpenKey(&deviceSubkeyHandle, KEY_READ, &objectAttributes);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
ZwClose(deviceParameterHandle);
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
defaultParameterValue = *ParameterValue;
|
|
|
|
queryTable->Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_TYPECHECK;
|
|
queryTable->Name = ParameterName;
|
|
queryTable->EntryContext = ParameterValue;
|
|
queryTable->DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
|
|
queryTable->DefaultData = NULL;
|
|
queryTable->DefaultLength = 0;
|
|
|
|
status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)(SubkeyName ?
|
|
deviceSubkeyHandle :
|
|
deviceParameterHandle),
|
|
queryTable,
|
|
NULL,
|
|
NULL);
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// Migrate the value over to the device-specific key
|
|
//
|
|
|
|
ClassSetDeviceParameter(FdoExtension, SubkeyName, ParameterName, *ParameterValue);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Use the default value
|
|
//
|
|
|
|
*ParameterValue = defaultParameterValue;
|
|
}
|
|
|
|
if (SubkeyName) {
|
|
ZwClose(deviceSubkeyHandle);
|
|
}
|
|
|
|
ZwClose(deviceParameterHandle);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
} // end ClassGetDeviceParameter()
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
NTSTATUS
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClassSetDeviceParameter(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_In_opt_ PWSTR SubkeyName,
|
|
_In_ PWSTR ParameterName,
|
|
_In_ ULONG ParameterValue)
|
|
{
|
|
NTSTATUS status;
|
|
HANDLE deviceParameterHandle = NULL;
|
|
HANDLE deviceSubkeyHandle = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// open the given parameter
|
|
//
|
|
|
|
status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
|
|
PLUGPLAY_REGKEY_DEVICE,
|
|
KEY_READ | KEY_WRITE,
|
|
&deviceParameterHandle);
|
|
|
|
if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
|
|
|
|
UNICODE_STRING subkeyName;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
|
|
RtlInitUnicodeString(&subkeyName, SubkeyName);
|
|
InitializeObjectAttributes(&objectAttributes,
|
|
&subkeyName,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
deviceParameterHandle,
|
|
NULL);
|
|
|
|
status = ZwCreateKey(&deviceSubkeyHandle,
|
|
KEY_READ | KEY_WRITE,
|
|
&objectAttributes,
|
|
0, NULL, 0, NULL);
|
|
if (!NT_SUCCESS(status)) {
|
|
ZwClose(deviceParameterHandle);
|
|
}
|
|
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
status = RtlWriteRegistryValue(
|
|
RTL_REGISTRY_HANDLE,
|
|
(PWSTR) (SubkeyName ?
|
|
deviceSubkeyHandle :
|
|
deviceParameterHandle),
|
|
ParameterName,
|
|
REG_DWORD,
|
|
&ParameterValue,
|
|
sizeof(ULONG));
|
|
|
|
//
|
|
// close what we open
|
|
//
|
|
|
|
if (SubkeyName) {
|
|
ZwClose(deviceSubkeyHandle);
|
|
}
|
|
|
|
ZwClose(deviceParameterHandle);
|
|
}
|
|
|
|
return status;
|
|
|
|
} // end ClassSetDeviceParameter()
|
|
|
|
|
|
/*
|
|
* ClassScanForSpecial
|
|
*
|
|
* This routine was written to simplify scanning for special
|
|
* hardware based upon id strings. it does not check the registry.
|
|
*/
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
VOID
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClassScanForSpecial(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_In_ CLASSPNP_SCAN_FOR_SPECIAL_INFO DeviceList[],
|
|
_In_ PCLASS_SCAN_FOR_SPECIAL_HANDLER Function)
|
|
{
|
|
PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor;
|
|
PUCHAR vendorId;
|
|
PUCHAR productId;
|
|
PUCHAR productRevision;
|
|
UCHAR nullString[] = "";
|
|
|
|
PAGED_CODE();
|
|
NT_ASSERT(DeviceList);
|
|
NT_ASSERT(Function);
|
|
|
|
deviceDescriptor = FdoExtension->DeviceDescriptor;
|
|
|
|
if (DeviceList == NULL) {
|
|
return;
|
|
}
|
|
if (Function == NULL) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// SCSI sets offsets to -1, ATAPI sets to 0. check for both.
|
|
//
|
|
|
|
if (deviceDescriptor->VendorIdOffset != 0 &&
|
|
deviceDescriptor->VendorIdOffset != -1) {
|
|
vendorId = ((PUCHAR)deviceDescriptor);
|
|
vendorId += deviceDescriptor->VendorIdOffset;
|
|
} else {
|
|
vendorId = nullString;
|
|
}
|
|
if (deviceDescriptor->ProductIdOffset != 0 &&
|
|
deviceDescriptor->ProductIdOffset != -1) {
|
|
productId = ((PUCHAR)deviceDescriptor);
|
|
productId += deviceDescriptor->ProductIdOffset;
|
|
} else {
|
|
productId = nullString;
|
|
}
|
|
if (deviceDescriptor->ProductRevisionOffset != 0 &&
|
|
deviceDescriptor->ProductRevisionOffset != -1) {
|
|
productRevision = ((PUCHAR)deviceDescriptor);
|
|
productRevision += deviceDescriptor->ProductRevisionOffset;
|
|
} else {
|
|
productRevision = nullString;
|
|
}
|
|
|
|
//
|
|
// loop while the device list is valid (not null-filled)
|
|
//
|
|
|
|
for (;(DeviceList->VendorId != NULL ||
|
|
DeviceList->ProductId != NULL ||
|
|
DeviceList->ProductRevision != NULL);DeviceList++) {
|
|
|
|
if (ClasspMyStringMatches(DeviceList->VendorId, (PCHAR)vendorId) &&
|
|
ClasspMyStringMatches(DeviceList->ProductId, (PCHAR)productId) &&
|
|
ClasspMyStringMatches(DeviceList->ProductRevision, (PCHAR)productRevision)
|
|
) {
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspScanForSpecialByInquiry: Found matching "
|
|
"controller Ven: %s Prod: %s Rev: %s\n",
|
|
(PCSZ)vendorId, (PCSZ)productId, (PCSZ)productRevision));
|
|
|
|
//
|
|
// pass the context to the call back routine and exit
|
|
//
|
|
|
|
(Function)(FdoExtension, DeviceList->Data);
|
|
|
|
//
|
|
// for CHK builds, try to prevent wierd stacks by having a debug
|
|
// print here. it's a hack, but i know of no other way to prevent
|
|
// the stack from being wrong.
|
|
//
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspScanForSpecialByInquiry: "
|
|
"completed callback\n"));
|
|
return;
|
|
|
|
} // else the strings did not match
|
|
|
|
} // none of the devices matched.
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspScanForSpecialByInquiry: no match found for %p\n",
|
|
FdoExtension->DeviceObject));
|
|
return;
|
|
|
|
} // end ClasspScanForSpecialByInquiry()
|
|
|
|
|
|
//
|
|
// In order to provide better performance without the need to reboot,
|
|
// we need to implement a self-adjusting method to set and clear the
|
|
// srb flags based upon current performance.
|
|
//
|
|
// whenever there is an error, immediately grab the spin lock. the
|
|
// MP perf hit here is acceptable, since we're in an error path. this
|
|
// is also neccessary because we are guaranteed to be modifying the
|
|
// SRB flags here, setting SuccessfulIO to zero, and incrementing the
|
|
// actual error count (which is always done within this spinlock).
|
|
//
|
|
// whenever there is no error, increment a counter. if there have been
|
|
// errors on the device, and we've enabled dynamic perf, *and* we've
|
|
// just crossed the perf threshhold, then grab the spin lock and
|
|
// double check that the threshhold has, indeed been hit(*). then
|
|
// decrement the error count, and if it's dropped sufficiently, undo
|
|
// some of the safety changes made in the SRB flags due to the errors.
|
|
//
|
|
// * this works in all cases. even if lots of ios occur after the
|
|
// previous guy went in and cleared the successfulio counter, that
|
|
// just means that we've hit the threshhold again, and so it's proper
|
|
// to run the inner loop again.
|
|
//
|
|
|
|
VOID
|
|
ClasspPerfIncrementErrorCount(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
|
|
KIRQL oldIrql;
|
|
ULONG errors;
|
|
|
|
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
|
|
|
|
fdoData->Perf.SuccessfulIO = 0; // implicit interlock
|
|
errors = InterlockedIncrement((volatile LONG *)&FdoExtension->ErrorCount);
|
|
|
|
if (!fdoData->DisableThrottling) {
|
|
|
|
if (errors >= CLASS_ERROR_LEVEL_1) {
|
|
|
|
//
|
|
// If the error count has exceeded the error limit, then disable
|
|
// any tagged queuing, multiple requests per lu queueing
|
|
// and sychronous data transfers.
|
|
//
|
|
// Clearing the no queue freeze flag prevents the port driver
|
|
// from sending multiple requests per logical unit.
|
|
//
|
|
|
|
CLEAR_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
|
|
CLEAR_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE);
|
|
|
|
SET_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementErrorCount: "
|
|
"Too many errors; disabling tagged queuing and "
|
|
"synchronous data tranfers.\n"));
|
|
|
|
}
|
|
|
|
if (errors >= CLASS_ERROR_LEVEL_2) {
|
|
|
|
//
|
|
// If a second threshold is reached, disable disconnects.
|
|
//
|
|
|
|
SET_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_DISABLE_DISCONNECT);
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementErrorCount: "
|
|
"Too many errors; disabling disconnects.\n"));
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
ClasspPerfIncrementSuccessfulIo(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
|
|
KIRQL oldIrql;
|
|
ULONG errors;
|
|
ULONG succeeded = 0;
|
|
|
|
//
|
|
// don't take a hit from the interlocked op unless we're in
|
|
// a degraded state and we've got a threshold to hit.
|
|
//
|
|
|
|
if (FdoExtension->ErrorCount == 0) {
|
|
return;
|
|
}
|
|
|
|
if (fdoData->Perf.ReEnableThreshhold == 0) {
|
|
return;
|
|
}
|
|
|
|
succeeded = InterlockedIncrement((volatile LONG *)&fdoData->Perf.SuccessfulIO);
|
|
if (succeeded < fdoData->Perf.ReEnableThreshhold) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// if we hit the threshold, grab the spinlock and verify we've
|
|
// actually done so. this allows us to ignore the spinlock 99%
|
|
// of the time.
|
|
//
|
|
|
|
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
|
|
|
|
//
|
|
// re-read the value, so we don't run this multiple times
|
|
// for a single threshhold being hit. this keeps errorcount
|
|
// somewhat useful.
|
|
//
|
|
|
|
succeeded = fdoData->Perf.SuccessfulIO;
|
|
|
|
if ((FdoExtension->ErrorCount != 0) &&
|
|
(fdoData->Perf.ReEnableThreshhold <= succeeded)
|
|
) {
|
|
|
|
fdoData->Perf.SuccessfulIO = 0; // implicit interlock
|
|
|
|
NT_ASSERT(FdoExtension->ErrorCount > 0);
|
|
errors = InterlockedDecrement((volatile LONG *)&FdoExtension->ErrorCount);
|
|
|
|
//
|
|
// note: do in reverse order of the sets "just in case"
|
|
//
|
|
|
|
if (errors < CLASS_ERROR_LEVEL_2) {
|
|
if (errors == CLASS_ERROR_LEVEL_2 - 1) {
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementSuccessfulIo: "
|
|
"Error level 2 no longer required.\n"));
|
|
}
|
|
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_DISABLE_DISCONNECT)) {
|
|
CLEAR_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_DISABLE_DISCONNECT);
|
|
}
|
|
}
|
|
|
|
if (errors < CLASS_ERROR_LEVEL_1) {
|
|
if (errors == CLASS_ERROR_LEVEL_1 - 1) {
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementSuccessfulIo: "
|
|
"Error level 1 no longer required.\n"));
|
|
}
|
|
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_DISABLE_SYNCH_TRANSFER)) {
|
|
CLEAR_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
|
|
}
|
|
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_QUEUE_ACTION_ENABLE)) {
|
|
SET_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_QUEUE_ACTION_ENABLE);
|
|
}
|
|
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_NO_QUEUE_FREEZE)) {
|
|
SET_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_NO_QUEUE_FREEZE);
|
|
}
|
|
}
|
|
} // end of threshhold definitely being hit for first time
|
|
|
|
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
|
|
return;
|
|
}
|
|
|
|
|
|
PMDL ClasspBuildDeviceMdl(PVOID Buffer, ULONG BufferLen, BOOLEAN WriteToDevice)
|
|
{
|
|
PMDL mdl;
|
|
|
|
mdl = IoAllocateMdl(Buffer, BufferLen, FALSE, FALSE, NULL);
|
|
if (mdl){
|
|
_SEH2_TRY {
|
|
MmProbeAndLockPages(mdl, KernelMode, WriteToDevice ? IoReadAccess : IoWriteAccess);
|
|
#ifdef _MSC_VER
|
|
#pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw
|
|
#endif
|
|
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
|
|
NTSTATUS status = _SEH2_GetExceptionCode();
|
|
|
|
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspBuildDeviceMdl: MmProbeAndLockPages failed with %xh.", status));
|
|
IoFreeMdl(mdl);
|
|
mdl = NULL;
|
|
} _SEH2_END;
|
|
}
|
|
else {
|
|
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspBuildDeviceMdl: IoAllocateMdl failed"));
|
|
}
|
|
|
|
return mdl;
|
|
}
|
|
|
|
|
|
PMDL BuildDeviceInputMdl(PVOID Buffer, ULONG BufferLen)
|
|
{
|
|
return ClasspBuildDeviceMdl(Buffer, BufferLen, FALSE);
|
|
}
|
|
|
|
|
|
VOID ClasspFreeDeviceMdl(PMDL Mdl)
|
|
{
|
|
MmUnlockPages(Mdl);
|
|
IoFreeMdl(Mdl);
|
|
}
|
|
|
|
|
|
VOID FreeDeviceInputMdl(PMDL Mdl)
|
|
{
|
|
ClasspFreeDeviceMdl(Mdl);
|
|
return;
|
|
}
|
|
|
|
|
|
#if 0
|
|
VOID
|
|
ClasspPerfResetCounters(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
|
|
KIRQL oldIrql;
|
|
|
|
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfResetCounters: "
|
|
"Resetting all perf counters.\n"));
|
|
fdoData->Perf.SuccessfulIO = 0;
|
|
FdoExtension->ErrorCount = 0;
|
|
|
|
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_DISABLE_DISCONNECT)) {
|
|
CLEAR_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_DISABLE_DISCONNECT);
|
|
}
|
|
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_DISABLE_SYNCH_TRANSFER)) {
|
|
CLEAR_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
|
|
}
|
|
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_QUEUE_ACTION_ENABLE)) {
|
|
SET_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_QUEUE_ACTION_ENABLE);
|
|
}
|
|
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
|
|
SRB_FLAGS_NO_QUEUE_FREEZE)) {
|
|
SET_FLAG(FdoExtension->SrbFlags,
|
|
SRB_FLAGS_NO_QUEUE_FREEZE);
|
|
}
|
|
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*++
|
|
|
|
ClasspDuidGetDeviceIdProperty
|
|
|
|
Routine Description:
|
|
|
|
Add StorageDeviceIdProperty to the device unique ID structure.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
Irp - a pointer to the I/O request packet
|
|
|
|
Return Value:
|
|
|
|
Status Code
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspDuidGetDeviceIdProperty(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PSTORAGE_DEVICE_ID_DESCRIPTOR deviceIdDescriptor = NULL;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSTORAGE_DESCRIPTOR_HEADER descHeader;
|
|
PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid;
|
|
PUCHAR dest;
|
|
|
|
STORAGE_PROPERTY_ID propertyId = StorageDeviceIdProperty;
|
|
|
|
NTSTATUS status;
|
|
|
|
ULONG queryLength;
|
|
ULONG offset;
|
|
|
|
//
|
|
// Get the VPD page 83h data.
|
|
//
|
|
|
|
status = ClassGetDescriptor(commonExtension->LowerDeviceObject,
|
|
&propertyId,
|
|
(PVOID *)&deviceIdDescriptor);
|
|
|
|
if (!NT_SUCCESS(status) || !deviceIdDescriptor) {
|
|
goto FnExit;
|
|
}
|
|
|
|
queryLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
descHeader = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Adjust required size and potential destination location.
|
|
//
|
|
|
|
offset = descHeader->Size;
|
|
dest = (PUCHAR)descHeader + offset;
|
|
|
|
descHeader->Size += deviceIdDescriptor->Size;
|
|
|
|
if (queryLength < descHeader->Size) {
|
|
|
|
//
|
|
// Output buffer is too small. Return error and make sure
|
|
// the caller gets info about required buffer size.
|
|
//
|
|
|
|
Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
goto FnExit;
|
|
}
|
|
|
|
storageDuid = Irp->AssociatedIrp.SystemBuffer;
|
|
storageDuid->StorageDeviceIdOffset = offset;
|
|
|
|
RtlCopyMemory(dest,
|
|
deviceIdDescriptor,
|
|
deviceIdDescriptor->Size);
|
|
|
|
Irp->IoStatus.Information = storageDuid->Size;
|
|
status = STATUS_SUCCESS;
|
|
|
|
FnExit:
|
|
|
|
FREE_POOL(deviceIdDescriptor);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
ClasspDuidGetDeviceProperty
|
|
|
|
Routine Description:
|
|
|
|
Add StorageDeviceProperty to the device unique ID structure.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
Irp - a pointer to the I/O request packet
|
|
|
|
Return Value:
|
|
|
|
Status Code
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspDuidGetDeviceProperty(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = fdoExtension->DeviceDescriptor;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSTORAGE_DESCRIPTOR_HEADER descHeader;
|
|
PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid;
|
|
PUCHAR dest;
|
|
|
|
NTSTATUS status = STATUS_NOT_FOUND;
|
|
|
|
ULONG queryLength;
|
|
ULONG offset;
|
|
|
|
//
|
|
// Use the StorageDeviceProperty already cached in the device extension.
|
|
//
|
|
|
|
if (!deviceDescriptor) {
|
|
goto FnExit;
|
|
}
|
|
|
|
queryLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
descHeader = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Use this info only if serial number is available.
|
|
//
|
|
|
|
if (deviceDescriptor->SerialNumberOffset == 0) {
|
|
goto FnExit;
|
|
}
|
|
|
|
//
|
|
// Adjust required size and potential destination location.
|
|
//
|
|
|
|
offset = descHeader->Size;
|
|
dest = (PUCHAR)descHeader + offset;
|
|
|
|
descHeader->Size += deviceDescriptor->Size;
|
|
|
|
if (queryLength < descHeader->Size) {
|
|
|
|
//
|
|
// Output buffer is too small. Return error and make sure
|
|
// the caller get info about required buffer size.
|
|
//
|
|
|
|
Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
goto FnExit;
|
|
}
|
|
|
|
storageDuid = Irp->AssociatedIrp.SystemBuffer;
|
|
storageDuid->StorageDeviceOffset = offset;
|
|
|
|
RtlCopyMemory(dest,
|
|
deviceDescriptor,
|
|
deviceDescriptor->Size);
|
|
|
|
Irp->IoStatus.Information = storageDuid->Size;
|
|
status = STATUS_SUCCESS;
|
|
|
|
FnExit:
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
ClasspDuidGetDriveLayout
|
|
|
|
Routine Description:
|
|
|
|
Add drive layout signature to the device unique ID structure.
|
|
Layout signature is only added for disk-type devices.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
Irp - a pointer to the I/O request packet
|
|
|
|
Return Value:
|
|
|
|
Status Code
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspDuidGetDriveLayout(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp
|
|
)
|
|
{
|
|
PDRIVE_LAYOUT_INFORMATION_EX layoutEx = NULL;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSTORAGE_DESCRIPTOR_HEADER descHeader;
|
|
PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid;
|
|
PSTORAGE_DEVICE_LAYOUT_SIGNATURE driveLayoutSignature;
|
|
|
|
NTSTATUS status = STATUS_NOT_FOUND;
|
|
|
|
ULONG queryLength;
|
|
ULONG offset;
|
|
|
|
//
|
|
// Only process disk-type devices.
|
|
//
|
|
|
|
if (DeviceObject->DeviceType != FILE_DEVICE_DISK) {
|
|
goto FnExit;
|
|
}
|
|
|
|
//
|
|
// Get current partition table and process only if GPT
|
|
// or MBR layout.
|
|
//
|
|
|
|
status = IoReadPartitionTableEx(DeviceObject, &layoutEx);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
status = STATUS_NOT_FOUND;
|
|
goto FnExit;
|
|
}
|
|
|
|
if (layoutEx->PartitionStyle != PARTITION_STYLE_GPT &&
|
|
layoutEx->PartitionStyle != PARTITION_STYLE_MBR) {
|
|
status = STATUS_NOT_FOUND;
|
|
goto FnExit;
|
|
}
|
|
|
|
queryLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
descHeader = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Adjust required size and potential destination location.
|
|
//
|
|
|
|
offset = descHeader->Size;
|
|
driveLayoutSignature = (PSTORAGE_DEVICE_LAYOUT_SIGNATURE)((PUCHAR)descHeader + offset);
|
|
|
|
descHeader->Size += sizeof(STORAGE_DEVICE_LAYOUT_SIGNATURE);
|
|
|
|
if (queryLength < descHeader->Size) {
|
|
|
|
//
|
|
// Output buffer is too small. Return error and make sure
|
|
// the caller get info about required buffer size.
|
|
//
|
|
|
|
Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
goto FnExit;
|
|
}
|
|
|
|
storageDuid = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
driveLayoutSignature->Size = sizeof(STORAGE_DEVICE_LAYOUT_SIGNATURE);
|
|
driveLayoutSignature->Version = DUID_VERSION_1;
|
|
|
|
if (layoutEx->PartitionStyle == PARTITION_STYLE_MBR) {
|
|
|
|
driveLayoutSignature->Mbr = TRUE;
|
|
|
|
RtlCopyMemory(&driveLayoutSignature->DeviceSpecific.MbrSignature,
|
|
&layoutEx->Mbr.Signature,
|
|
sizeof(layoutEx->Mbr.Signature));
|
|
|
|
} else {
|
|
|
|
driveLayoutSignature->Mbr = FALSE;
|
|
|
|
RtlCopyMemory(&driveLayoutSignature->DeviceSpecific.GptDiskId,
|
|
&layoutEx->Gpt.DiskId,
|
|
sizeof(layoutEx->Gpt.DiskId));
|
|
}
|
|
|
|
storageDuid->DriveLayoutSignatureOffset = offset;
|
|
|
|
Irp->IoStatus.Information = storageDuid->Size;
|
|
status = STATUS_SUCCESS;
|
|
|
|
|
|
FnExit:
|
|
|
|
FREE_POOL(layoutEx);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
ClasspDuidQueryProperty
|
|
|
|
Routine Description:
|
|
|
|
Handles IOCTL_STORAGE_QUERY_PROPERTY for device unique ID requests
|
|
(when PropertyId is StorageDeviceUniqueIdProperty).
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
Irp - a pointer to the I/O request packet
|
|
|
|
Return Value:
|
|
|
|
Status Code
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspDuidQueryProperty(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp
|
|
)
|
|
{
|
|
PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer;
|
|
PSTORAGE_DESCRIPTOR_HEADER descHeader;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
NTSTATUS status;
|
|
|
|
ULONG outLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
BOOLEAN includeOptionalIds;
|
|
BOOLEAN overflow = FALSE;
|
|
BOOLEAN infoFound = FALSE;
|
|
BOOLEAN useStatus = TRUE; // Use the status directly instead of relying on overflow and infoFound flags.
|
|
|
|
//
|
|
// Must run at less then dispatch.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto FnExit;
|
|
}
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
Irp->IoStatus.Information = 0;
|
|
status = STATUS_SUCCESS;
|
|
goto FnExit;
|
|
}
|
|
|
|
if (query->QueryType != PropertyStandardQuery) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto FnExit;
|
|
}
|
|
|
|
//
|
|
// Check AdditionalParameters validity.
|
|
//
|
|
|
|
if (query->AdditionalParameters[0] == DUID_INCLUDE_SOFTWARE_IDS) {
|
|
includeOptionalIds = TRUE;
|
|
} else if (query->AdditionalParameters[0] == DUID_HARDWARE_IDS_ONLY) {
|
|
includeOptionalIds = FALSE;
|
|
} else {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto FnExit;
|
|
}
|
|
|
|
//
|
|
// Verify output parameters.
|
|
//
|
|
|
|
if (outLength < sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto FnExit;
|
|
}
|
|
|
|
//
|
|
// From this point forward the status depends on the overflow
|
|
// and infoFound flags.
|
|
//
|
|
|
|
useStatus = FALSE;
|
|
|
|
descHeader = Irp->AssociatedIrp.SystemBuffer;
|
|
RtlZeroMemory(descHeader, outLength);
|
|
|
|
descHeader->Version = DUID_VERSION_1;
|
|
descHeader->Size = sizeof(STORAGE_DEVICE_UNIQUE_IDENTIFIER);
|
|
|
|
//
|
|
// Try to build device unique id from StorageDeviceIdProperty.
|
|
//
|
|
|
|
status = ClasspDuidGetDeviceIdProperty(DeviceObject,
|
|
Irp);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW) {
|
|
overflow = TRUE;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
infoFound = TRUE;
|
|
}
|
|
|
|
//
|
|
// Try to build device unique id from StorageDeviceProperty.
|
|
//
|
|
|
|
status = ClasspDuidGetDeviceProperty(DeviceObject,
|
|
Irp);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW) {
|
|
overflow = TRUE;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
infoFound = TRUE;
|
|
}
|
|
|
|
//
|
|
// The following portion is optional and only included if the
|
|
// caller requested software IDs.
|
|
//
|
|
|
|
if (!includeOptionalIds) {
|
|
goto FnExit;
|
|
}
|
|
|
|
//
|
|
// Try to build device unique id from drive layout signature (disk
|
|
// devices only).
|
|
//
|
|
|
|
status = ClasspDuidGetDriveLayout(DeviceObject,
|
|
Irp);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW) {
|
|
overflow = TRUE;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
infoFound = TRUE;
|
|
}
|
|
|
|
FnExit:
|
|
|
|
if (!useStatus) {
|
|
|
|
//
|
|
// Return overflow, success, or a generic error.
|
|
//
|
|
|
|
if (overflow) {
|
|
|
|
//
|
|
// If output buffer is STORAGE_DESCRIPTOR_HEADER, then return
|
|
// success to the user. Otherwise, send an error so the user
|
|
// knows a larger buffer is required.
|
|
//
|
|
|
|
if (outLength == sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
} else {
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
} else if (infoFound) {
|
|
status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Exercise the compare routine. This should always succeed.
|
|
//
|
|
|
|
NT_ASSERT(DuidExactMatch == CompareStorageDuids(Irp->AssociatedIrp.SystemBuffer,
|
|
Irp->AssociatedIrp.SystemBuffer));
|
|
|
|
} else {
|
|
status = STATUS_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClasspWriteCacheProperty()
|
|
|
|
Routine Description:
|
|
|
|
This routine reads the caching mode page from the device to
|
|
build the Write Cache property page.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - The device object to handle this irp
|
|
|
|
Irp - The IRP for this request
|
|
|
|
Srb - SRB allocated by the dispatch routine
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
NTSTATUS ClasspWriteCacheProperty(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
PSTORAGE_WRITE_CACHE_PROPERTY writeCache;
|
|
PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PMODE_PARAMETER_HEADER modeData = NULL;
|
|
PMODE_CACHING_PAGE pageData = NULL;
|
|
ULONG length, information = 0;
|
|
NTSTATUS status;
|
|
PCDB cdb;
|
|
|
|
//
|
|
// Must run at less then dispatch.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto WriteCacheExit;
|
|
}
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
status = STATUS_SUCCESS;
|
|
goto WriteCacheExit;
|
|
}
|
|
|
|
if (query->QueryType != PropertyStandardQuery) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto WriteCacheExit;
|
|
}
|
|
|
|
length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
if (length < sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto WriteCacheExit;
|
|
}
|
|
|
|
writeCache = (PSTORAGE_WRITE_CACHE_PROPERTY) Irp->AssociatedIrp.SystemBuffer;
|
|
RtlZeroMemory(writeCache, length);
|
|
|
|
//
|
|
// Set version and required size.
|
|
//
|
|
|
|
writeCache->Version = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
|
|
writeCache->Size = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
|
|
|
|
if (length < sizeof(STORAGE_WRITE_CACHE_PROPERTY)) {
|
|
information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
status = STATUS_SUCCESS;
|
|
goto WriteCacheExit;
|
|
}
|
|
|
|
//
|
|
// Set known values
|
|
//
|
|
|
|
writeCache->NVCacheEnabled = FALSE;
|
|
writeCache->UserDefinedPowerProtection = TEST_FLAG(fdoExtension->DeviceFlags, DEV_POWER_PROTECTED);
|
|
|
|
//
|
|
// Check for flush cache support by sending a sync cache command
|
|
// to the device.
|
|
//
|
|
|
|
//
|
|
// Set timeout value and mark the request as not being a tagged request.
|
|
//
|
|
SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue * 4);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);
|
|
|
|
SrbSetCdbLength(Srb, 10);
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->CDB10.OperationCode = SCSIOP_SYNCHRONIZE_CACHE;
|
|
|
|
status = ClassSendSrbSynchronous(DeviceObject,
|
|
Srb,
|
|
NULL,
|
|
0,
|
|
TRUE);
|
|
if (NT_SUCCESS(status)) {
|
|
writeCache->FlushCacheSupported = TRUE;
|
|
} else {
|
|
//
|
|
// Device does not support sync cache
|
|
//
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Synchronize cache failed with status 0x%X\n", status));
|
|
writeCache->FlushCacheSupported = FALSE;
|
|
//
|
|
// Reset the status if there was any failure
|
|
//
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
modeData = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
|
|
MODE_PAGE_DATA_SIZE,
|
|
CLASS_TAG_MODE_DATA);
|
|
|
|
if (modeData == NULL) {
|
|
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Unable to allocate mode data buffer\n"));
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto WriteCacheExit;
|
|
}
|
|
|
|
RtlZeroMemory(modeData, MODE_PAGE_DATA_SIZE);
|
|
|
|
length = ClassModeSense(DeviceObject,
|
|
(PCHAR) modeData,
|
|
MODE_PAGE_DATA_SIZE,
|
|
MODE_PAGE_CACHING);
|
|
|
|
if (length < sizeof(MODE_PARAMETER_HEADER)) {
|
|
|
|
//
|
|
// Retry the request in case of a check condition.
|
|
//
|
|
|
|
length = ClassModeSense(DeviceObject,
|
|
(PCHAR) modeData,
|
|
MODE_PAGE_DATA_SIZE,
|
|
MODE_PAGE_CACHING);
|
|
|
|
if (length < sizeof(MODE_PARAMETER_HEADER)) {
|
|
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Mode Sense failed\n"));
|
|
status = STATUS_IO_DEVICE_ERROR;
|
|
goto WriteCacheExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the length is greater than length indicated by the mode data reset
|
|
// the data to the mode data.
|
|
//
|
|
|
|
if (length > (ULONG) (modeData->ModeDataLength + 1)) {
|
|
length = modeData->ModeDataLength + 1;
|
|
}
|
|
|
|
//
|
|
// Look for caching page in the returned mode page data.
|
|
//
|
|
|
|
pageData = ClassFindModePage((PCHAR) modeData,
|
|
length,
|
|
MODE_PAGE_CACHING,
|
|
TRUE);
|
|
|
|
//
|
|
// Check if valid caching page exists.
|
|
//
|
|
|
|
if (pageData == NULL) {
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Unable to find caching mode page.\n"));
|
|
//
|
|
// Set write cache value as unknown.
|
|
//
|
|
writeCache->WriteCacheEnabled = WriteCacheEnableUnknown;
|
|
writeCache->WriteCacheType = WriteCacheTypeUnknown;
|
|
} else {
|
|
writeCache->WriteCacheEnabled = pageData->WriteCacheEnable ?
|
|
WriteCacheEnabled : WriteCacheDisabled;
|
|
|
|
writeCache->WriteCacheType = pageData->WriteCacheEnable ?
|
|
WriteCacheTypeWriteBack : WriteCacheTypeUnknown;
|
|
}
|
|
|
|
//
|
|
// Check write through support. If the device previously failed a write request
|
|
// with FUA bit is set, then CLASS_SPECIAL_FUA_NOT_SUPPORTED will be set,
|
|
// which means write through is not support by the device.
|
|
//
|
|
|
|
if ((modeData->DeviceSpecificParameter & MODE_DSP_FUA_SUPPORTED) &&
|
|
(!TEST_FLAG(fdoExtension->ScanForSpecialFlags, CLASS_SPECIAL_FUA_NOT_SUPPORTED))) {
|
|
writeCache->WriteThroughSupported = WriteThroughSupported;
|
|
} else {
|
|
writeCache->WriteThroughSupported = WriteThroughNotSupported;
|
|
}
|
|
|
|
//
|
|
// Get the changeable caching mode page and check write cache is changeable.
|
|
//
|
|
|
|
RtlZeroMemory(modeData, MODE_PAGE_DATA_SIZE);
|
|
|
|
length = ClasspModeSense(DeviceObject,
|
|
(PCHAR) modeData,
|
|
MODE_PAGE_DATA_SIZE,
|
|
MODE_PAGE_CACHING,
|
|
MODE_SENSE_CHANGEABLE_VALUES);
|
|
|
|
if (length < sizeof(MODE_PARAMETER_HEADER)) {
|
|
|
|
//
|
|
// Retry the request in case of a check condition.
|
|
//
|
|
|
|
length = ClasspModeSense(DeviceObject,
|
|
(PCHAR) modeData,
|
|
MODE_PAGE_DATA_SIZE,
|
|
MODE_PAGE_CACHING,
|
|
MODE_SENSE_CHANGEABLE_VALUES);
|
|
|
|
if (length < sizeof(MODE_PARAMETER_HEADER)) {
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Mode Sense failed\n"));
|
|
|
|
//
|
|
// If the device fails to return changeable pages, then
|
|
// set the write cache changeable value to unknown.
|
|
//
|
|
|
|
writeCache->WriteCacheChangeable = WriteCacheChangeUnknown;
|
|
information = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
|
|
goto WriteCacheExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the length is greater than length indicated by the mode data reset
|
|
// the data to the mode data.
|
|
//
|
|
|
|
if (length > (ULONG) (modeData->ModeDataLength + 1)) {
|
|
length = modeData->ModeDataLength + 1;
|
|
}
|
|
|
|
//
|
|
// Look for caching page in the returned mode page data.
|
|
//
|
|
|
|
pageData = ClassFindModePage((PCHAR) modeData,
|
|
length,
|
|
MODE_PAGE_CACHING,
|
|
TRUE);
|
|
//
|
|
// Check if valid caching page exists.
|
|
//
|
|
|
|
if (pageData == NULL) {
|
|
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Unable to find caching mode page.\n"));
|
|
//
|
|
// Set write cache changeable value to unknown.
|
|
//
|
|
writeCache->WriteCacheChangeable = WriteCacheChangeUnknown;
|
|
} else {
|
|
writeCache->WriteCacheChangeable = pageData->WriteCacheEnable ?
|
|
WriteCacheChangeable : WriteCacheNotChangeable;
|
|
}
|
|
|
|
information = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
|
|
|
|
WriteCacheExit:
|
|
|
|
FREE_POOL(modeData);
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = information;;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
ULONG
|
|
ClasspCalculateLogicalSectorSize (
|
|
_In_ PDEVICE_OBJECT Fdo,
|
|
_In_ ULONG BytesPerBlockInBigEndian
|
|
)
|
|
/*++
|
|
Convert the big-endian value.
|
|
if it's 0, default to the standard 512 bytes.
|
|
if it's not a power of 2 value, round down to power of 2.
|
|
--*/
|
|
{
|
|
ULONG logicalSectorSize;
|
|
|
|
REVERSE_BYTES(&logicalSectorSize, &BytesPerBlockInBigEndian);
|
|
|
|
if (logicalSectorSize == 0) {
|
|
logicalSectorSize = 512;
|
|
} else {
|
|
//
|
|
// Clear all but the highest set bit.
|
|
// That will give us a bytesPerSector value that is a power of 2.
|
|
//
|
|
if (logicalSectorSize & (logicalSectorSize-1)) {
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "FDO %ph has non-standard sector size 0x%x.", Fdo, logicalSectorSize));
|
|
do {
|
|
logicalSectorSize &= logicalSectorSize-1;
|
|
}
|
|
while (logicalSectorSize & (logicalSectorSize-1));
|
|
}
|
|
}
|
|
|
|
return logicalSectorSize;
|
|
}
|
|
|
|
NTSTATUS
|
|
InterpretReadCapacity16Data (
|
|
_Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_In_ PREAD_CAPACITY16_DATA ReadCapacity16Data
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
USHORT lowestAlignedBlock;
|
|
USHORT logicalBlocksPerPhysicalBlock;
|
|
PCLASS_READ_CAPACITY16_DATA cachedData = &(FdoExtension->FunctionSupportInfo->ReadCapacity16Data);
|
|
|
|
// use Logical Sector Size from DiskGeometry to avoid duplicated calculation.
|
|
FdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector = ClasspCalculateLogicalSectorSize(FdoExtension->DeviceObject, ReadCapacity16Data->BytesPerBlock);
|
|
|
|
// FdoExtension->DiskGeometry.BytesPerSector might be 0 for class drivers that don't get READ CAPACITY info yet.
|
|
NT_ASSERT( (FdoExtension->DiskGeometry.BytesPerSector == 0) ||
|
|
(FdoExtension->DiskGeometry.BytesPerSector == FdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector) );
|
|
|
|
logicalBlocksPerPhysicalBlock = 1 << ReadCapacity16Data->LogicalPerPhysicalExponent;
|
|
lowestAlignedBlock = (ReadCapacity16Data->LowestAlignedBlock_MSB << 8) | ReadCapacity16Data->LowestAlignedBlock_LSB;
|
|
|
|
if (lowestAlignedBlock > logicalBlocksPerPhysicalBlock) {
|
|
// we get garbage data
|
|
status = STATUS_UNSUCCESSFUL;
|
|
} else {
|
|
// value of lowestAlignedBlock (from T10 spec) needs to be converted.
|
|
lowestAlignedBlock = (logicalBlocksPerPhysicalBlock - lowestAlignedBlock) % logicalBlocksPerPhysicalBlock;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
// fill output buffer
|
|
cachedData->BytesPerPhysicalSector = cachedData->BytesPerLogicalSector * logicalBlocksPerPhysicalBlock;
|
|
cachedData->BytesOffsetForSectorAlignment = cachedData->BytesPerLogicalSector * lowestAlignedBlock;
|
|
|
|
//
|
|
// Fill in the Logical Block Provisioning info. Note that we do not
|
|
// use these fields; we use the Provisioning Type and LBPRZ fields from
|
|
// the Logical Block Provisioning VPD page (0xB2).
|
|
//
|
|
cachedData->LBProvisioningEnabled = ReadCapacity16Data->LBPME;
|
|
cachedData->LBProvisioningReadZeros = ReadCapacity16Data->LBPRZ;
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_INIT,
|
|
"InterpretReadCapacity16Data: Device\'s LBP enabled = %d\n",
|
|
cachedData->LBProvisioningEnabled));
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
ClassReadCapacity16 (
|
|
_Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
This routine may send down a READ CAPACITY 16 command to retrieve info.
|
|
The info will be cached in FdoExtension->LowerLayerSupport->AccessAlignment.
|
|
|
|
After info retrieving finished, this function sets following field:
|
|
FdoExtension->LowerLayerSupport->AccessAlignment.LowerLayerSupported = Supported;
|
|
to indicate that info has been cached.
|
|
|
|
NOTE: some future processes may use this function to send the command anyway, it will be caller's decision
|
|
on checking 'AccessAlignment.LowerLayerSupported' in case the cached info is good enough.
|
|
*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PREAD_CAPACITY16_DATA dataBuffer = NULL;
|
|
UCHAR bufferLength = sizeof(READ_CAPACITY16_DATA);
|
|
ULONG allocationBufferLength = bufferLength; //DMA buffer size for alignment
|
|
PCDB cdb;
|
|
ULONG dataTransferLength = 0;
|
|
|
|
//
|
|
// If the information retrieval has already been attempted, return the cached status.
|
|
//
|
|
if (FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus != -1) {
|
|
// get cached NTSTATUS from previous call.
|
|
return FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus;
|
|
}
|
|
|
|
if (ClasspIsObsoletePortDriver(FdoExtension)) {
|
|
FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = STATUS_NOT_IMPLEMENTED;
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
#if defined(_ARM_) || defined(_ARM64_)
|
|
//
|
|
// ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
|
|
// based platforms. We are taking the conservative approach here.
|
|
//
|
|
allocationBufferLength = ALIGN_UP_BY(allocationBufferLength,KeGetRecommendedSharedDataAlignment());
|
|
dataBuffer = (PREAD_CAPACITY16_DATA)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength, '4CcS');
|
|
#else
|
|
dataBuffer = (PREAD_CAPACITY16_DATA)ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, '4CcS');
|
|
#endif
|
|
|
|
if (dataBuffer == NULL) {
|
|
// return without updating FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus
|
|
// the field will remain value as "-1", so that the command will be attempted next time this function is called.
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(dataBuffer, allocationBufferLength);
|
|
|
|
//
|
|
// Initialize the SRB.
|
|
//
|
|
if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
|
|
status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)Srb,
|
|
STORAGE_ADDRESS_TYPE_BTL8,
|
|
CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
|
|
1,
|
|
SrbExDataTypeScsiCdb16);
|
|
if (NT_SUCCESS(status)) {
|
|
((PSTORAGE_REQUEST_BLOCK)Srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
|
|
} else {
|
|
//
|
|
// Should not occur.
|
|
//
|
|
NT_ASSERT(FALSE);
|
|
}
|
|
} else {
|
|
RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
}
|
|
|
|
//prepare the Srb
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
|
|
SrbSetTimeOutValue(Srb, FdoExtension->TimeOutValue);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
SrbAssignSrbFlags(Srb, FdoExtension->SrbFlags);
|
|
|
|
SrbSetCdbLength(Srb, 16);
|
|
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->READ_CAPACITY16.OperationCode = SCSIOP_READ_CAPACITY16;
|
|
cdb->READ_CAPACITY16.ServiceAction = SERVICE_ACTION_READ_CAPACITY16;
|
|
cdb->READ_CAPACITY16.AllocationLength[3] = bufferLength;
|
|
|
|
status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
|
|
Srb,
|
|
dataBuffer,
|
|
allocationBufferLength,
|
|
FALSE);
|
|
|
|
dataTransferLength = SrbGetDataTransferLength(Srb);
|
|
}
|
|
|
|
if (NT_SUCCESS(status) && (dataTransferLength < 16))
|
|
{
|
|
// the device should return at least 16 bytes of data for this command.
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
}
|
|
|
|
//
|
|
// Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
|
|
// buffer was larger than necessary.
|
|
//
|
|
if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength)
|
|
{
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
// cache data into FdoExtension
|
|
status = InterpretReadCapacity16Data(FdoExtension, dataBuffer);
|
|
}
|
|
|
|
// cache the status indicates that this funciton has been called.
|
|
FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = status;
|
|
|
|
ExFreePool(dataBuffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClasspAccessAlignmentProperty(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
|
|
If it's not supported, SCSIOP_READ_CAPACITY16 will be sent down to retrieve the information.
|
|
*/
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
ULONG length = 0;
|
|
ULONG information = 0;
|
|
|
|
PSTORAGE_ACCESS_ALIGNMENT_DESCRIPTOR accessAlignment;
|
|
|
|
//
|
|
// check registry setting and fail the IOCTL if it's required.
|
|
// this registry setting can be used to work around issues which upper layer doesn't support large physical sector size.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->RegAccessAlignmentQueryNotSupported) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
|
|
if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
|
|
(TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
|
|
(fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty == Supported) ) {
|
|
// if it's not disk, forward the request to lower layer,
|
|
// if the IOCTL is supported by lower stack, forward it down.
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
} else if (query->QueryType != PropertyStandardQuery) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Request validation.
|
|
// Note that InputBufferLength and IsFdo have been validated before entering this routine.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto Exit;
|
|
}
|
|
|
|
// do not touch this buffer because it can still be used as input buffer for lower layer in 'SupportUnknown' case.
|
|
accessAlignment = (PSTORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
if (length < sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)) {
|
|
|
|
if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
|
|
information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
accessAlignment->Version = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
accessAlignment->Size = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
}
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto Exit;
|
|
}
|
|
|
|
// not support Cache Line,
|
|
// 'BytesPerCacheLine' and 'BytesOffsetForCacheAlignment' fields are zero-ed.
|
|
|
|
//
|
|
// note that 'Supported' case has been handled at the beginning of this function.
|
|
//
|
|
switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty) {
|
|
case SupportUnknown: {
|
|
// send down request and wait for the request to complete.
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
|
|
if (ClasspLowerLayerNotSupport(status)) {
|
|
// case 1: the request is not supported by lower layer, sends down command
|
|
// some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
|
|
|
|
// ClassReadCapacity16() will either return status from cached data or send command to retrieve the information.
|
|
if (ClasspIsObsoletePortDriver(fdoExtension) == FALSE) {
|
|
status = ClassReadCapacity16(fdoExtension, Srb);
|
|
} else {
|
|
fdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = status;
|
|
}
|
|
|
|
// data is ready in fdoExtension
|
|
// set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty = NotSupported;
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
// fill output buffer
|
|
RtlZeroMemory(accessAlignment, length);
|
|
accessAlignment->Version = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
accessAlignment->Size = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
accessAlignment->BytesPerLogicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector;
|
|
accessAlignment->BytesPerPhysicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerPhysicalSector;
|
|
accessAlignment->BytesOffsetForSectorAlignment = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesOffsetForSectorAlignment;
|
|
|
|
// set returned data length
|
|
information = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
} else {
|
|
information = 0;
|
|
}
|
|
|
|
} else {
|
|
// case 2: the request is supported and it completes successfully
|
|
// case 3: the request is supported by lower stack but other failure status is returned.
|
|
// from now on, the same request will be send down to lower stack directly.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty = Supported;
|
|
information = (ULONG)Irp->IoStatus.Information;
|
|
|
|
|
|
}
|
|
|
|
|
|
goto Exit;
|
|
|
|
break;
|
|
}
|
|
|
|
case NotSupported: {
|
|
|
|
// ClassReadCapacity16() will either return status from cached data or send command to retrieve the information.
|
|
status = ClassReadCapacity16(fdoExtension, Srb);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
RtlZeroMemory(accessAlignment, length);
|
|
accessAlignment->Version = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
accessAlignment->Size = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
accessAlignment->BytesPerLogicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector;
|
|
accessAlignment->BytesPerPhysicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerPhysicalSector;
|
|
accessAlignment->BytesOffsetForSectorAlignment = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesOffsetForSectorAlignment;
|
|
|
|
information = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
|
|
} else {
|
|
information = 0;
|
|
}
|
|
goto Exit;
|
|
|
|
break;
|
|
}
|
|
|
|
case Supported: {
|
|
NT_ASSERT(FALSE); // this case is handled at the beginning of the function.
|
|
status = STATUS_INTERNAL_ERROR;
|
|
break;
|
|
}
|
|
|
|
} // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty)
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = information;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IncursSeekPenalty (
|
|
_In_ USHORT MediumRotationRate,
|
|
_In_ PBOOLEAN IncursSeekPenalty
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (MediumRotationRate == 0x0001) {
|
|
// Non-rotating media (e.g., solid state device)
|
|
*IncursSeekPenalty = FALSE;
|
|
status = STATUS_SUCCESS;
|
|
} else if ( (MediumRotationRate >= 0x401) &&
|
|
(MediumRotationRate <= 0xFFFE) ) {
|
|
// Nominal media rotation rate in rotations per minute (rpm)
|
|
*IncursSeekPenalty = TRUE;
|
|
status = STATUS_SUCCESS;
|
|
} else {
|
|
// Unknown cases:
|
|
// 0 - Rate not reported
|
|
// 0002h-0400h - Reserved
|
|
// FFFFh - Reserved
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
ClasspDeviceMediaTypeProperty(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the medium product type reported by the device for the associated LU.
|
|
|
|
This function must be called at IRQL < DISPATCH_LEVEL.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request
|
|
Irp - The IRP to be processed
|
|
Srb - The SRB associated with the request
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
|
|
PSTORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR pDesc = (PSTORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION irpStack;
|
|
ULONG length = 0;
|
|
ULONG information = 0;
|
|
|
|
irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceMediaTypeProperty (%p): Entering function.\n",
|
|
DeviceObject));
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
|
|
//
|
|
// In order to maintain consistency with the how the rest of the properties
|
|
// are handled, always return success for PropertyExistsQuery.
|
|
//
|
|
status = STATUS_SUCCESS;
|
|
goto __ClasspDeviceMediaTypeProperty_Exit;
|
|
|
|
} else if (query->QueryType != PropertyStandardQuery) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceMediaTypeProperty (%p): Unsupported query type %x for media type property.\n",
|
|
DeviceObject,
|
|
query->QueryType));
|
|
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto __ClasspDeviceMediaTypeProperty_Exit;
|
|
}
|
|
|
|
//
|
|
// Validate the request.
|
|
// InputBufferLength and IsFdo have already been validated.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceMediaTypeProperty (%p): Query property for media type at incorrect IRQL.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto __ClasspDeviceMediaTypeProperty_Exit;
|
|
}
|
|
|
|
length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
|
|
information = sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR);
|
|
pDesc->Version = sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR);
|
|
pDesc->Size = sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR);
|
|
} else {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto __ClasspDeviceMediaTypeProperty_Exit;
|
|
}
|
|
|
|
if (length < sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR)) {
|
|
|
|
status = STATUS_SUCCESS;
|
|
goto __ClasspDeviceMediaTypeProperty_Exit;
|
|
}
|
|
|
|
//
|
|
// Only query BlockDeviceCharacteristics VPD page if device support has been confirmed.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics == TRUE) {
|
|
status = ClasspDeviceGetBlockDeviceCharacteristicsVPDPage(fdoExtension, Srb);
|
|
} else {
|
|
//
|
|
// Otherwise device was previously found lacking support for this VPD page. Fail the request.
|
|
//
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
goto __ClasspDeviceMediaTypeProperty_Exit;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
status = fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus;
|
|
information = 0;
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetBlockDeviceCharacteristicsVPDPage (%p): VPD retrieval fails with %x.\n",
|
|
DeviceObject,
|
|
status));
|
|
|
|
goto __ClasspDeviceMediaTypeProperty_Exit;
|
|
}
|
|
|
|
//
|
|
// Fill in the output buffer. All data is copied from the FDO extension, cached
|
|
// from device response to earlier VPD_BLOCK_DEVICE_CHARACTERISTICS query.
|
|
//
|
|
pDesc->MediumProductType = fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumProductType;
|
|
status = STATUS_SUCCESS;
|
|
|
|
__ClasspDeviceMediaTypeProperty_Exit:
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = information;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceMediaTypeProperty (%p): Exiting function with status %x.\n",
|
|
DeviceObject,
|
|
status));
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClasspDeviceGetBlockDeviceCharacteristicsVPDPage(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension,
|
|
_In_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function sends an INQUIRY command request for VPD_BLOCK_DEVICE_CHARACTERISTICS to
|
|
the device. Relevant data from the response is cached in the FDO extension.
|
|
|
|
Arguments:
|
|
FdoExtension: The FDO extension of the device to which the INQUIRY command will be sent.
|
|
Srb: Allocated by the caller.
|
|
SrbSize: The size of the Srb buffer in bytes.
|
|
|
|
Return Value:
|
|
|
|
STATUS_INVALID_PARAMETER: May be returned if the LogPage buffer is NULL or
|
|
not large enough.
|
|
STATUS_SUCCESS: The log page was obtained and placed in the LogPage buffer.
|
|
|
|
This function may return other NTSTATUS codes from internal function calls.
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PCDB cdb;
|
|
UCHAR bufferLength = sizeof(VPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE); // data is 64 bytes
|
|
ULONG allocationBufferLength = bufferLength;
|
|
PVPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE dataBuffer = NULL;
|
|
|
|
|
|
#if defined(_ARM_) || defined(_ARM64_)
|
|
//
|
|
// ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
|
|
// based platforms. We are taking the conservative approach here.
|
|
//
|
|
allocationBufferLength = ALIGN_UP_BY(allocationBufferLength,KeGetRecommendedSharedDataAlignment());
|
|
dataBuffer = (PVPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
|
|
allocationBufferLength,
|
|
'5CcS'
|
|
);
|
|
#else
|
|
|
|
dataBuffer = (PVPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE)ExAllocatePoolWithTag(NonPagedPoolNx,
|
|
bufferLength,
|
|
'5CcS'
|
|
);
|
|
#endif
|
|
if (dataBuffer == NULL) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Exit;
|
|
}
|
|
|
|
RtlZeroMemory(dataBuffer, allocationBufferLength);
|
|
|
|
// prepare the Srb
|
|
SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);
|
|
|
|
SrbSetCdbLength(Srb, 6);
|
|
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
|
|
cdb->CDB6INQUIRY3.EnableVitalProductData = 1; //EVPD bit
|
|
cdb->CDB6INQUIRY3.PageCode = VPD_BLOCK_DEVICE_CHARACTERISTICS;
|
|
cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte.
|
|
|
|
status = ClassSendSrbSynchronous(fdoExtension->CommonExtension.DeviceObject,
|
|
Srb,
|
|
dataBuffer,
|
|
allocationBufferLength,
|
|
FALSE);
|
|
if (NT_SUCCESS(status)) {
|
|
if (SrbGetDataTransferLength(Srb) < 0x8) {
|
|
// the device should return at least 8 bytes of data for use.
|
|
status = STATUS_UNSUCCESSFUL;
|
|
} else if ( (dataBuffer->PageLength != 0x3C) || (dataBuffer->PageCode != VPD_BLOCK_DEVICE_CHARACTERISTICS) ) {
|
|
// 'PageLength' shall be 0x3C; and 'PageCode' shall match.
|
|
status = STATUS_UNSUCCESSFUL;
|
|
} else {
|
|
// cache data into fdoExtension
|
|
fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumRotationRate = (dataBuffer->MediumRotationRateMsb << 8) |
|
|
dataBuffer->MediumRotationRateLsb;
|
|
fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumProductType = dataBuffer->MediumProductType;
|
|
fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.NominalFormFactor = dataBuffer->NominalFormFactor;
|
|
}
|
|
} else {
|
|
// the command failed, surface up the command error from 'status' variable. Nothing to do here.
|
|
}
|
|
|
|
Exit:
|
|
if (dataBuffer != NULL) {
|
|
ExFreePool(dataBuffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClasspDeviceSeekPenaltyProperty(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
|
|
If it's not supported, INQUIRY (Block Device Characteristics VPD page) will be sent down to retrieve the information.
|
|
*/
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
ULONG length = 0;
|
|
ULONG information = 0;
|
|
BOOLEAN incursSeekPenalty = TRUE;
|
|
PDEVICE_SEEK_PENALTY_DESCRIPTOR seekPenalty;
|
|
|
|
if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
|
|
(TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
|
|
(fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty == Supported) ) {
|
|
// if it's not disk, forward the request to lower layer,
|
|
// if the IOCTL is supported by lower stack, forward it down.
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
} else if (query->QueryType != PropertyStandardQuery) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Request validation.
|
|
// Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto Exit;
|
|
}
|
|
|
|
// do not touch this buffer because it can still be used as input buffer for lower layer in 'SupportUnknown' case.
|
|
seekPenalty = (PDEVICE_SEEK_PENALTY_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
if (length < sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR)) {
|
|
|
|
if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
|
|
information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
seekPenalty->Version = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
seekPenalty->Size = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
}
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// note that 'Supported' case has been handled at the beginning of this function.
|
|
//
|
|
switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty) {
|
|
case SupportUnknown: {
|
|
// send down request and wait for the request to complete.
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
|
|
if (ClasspLowerLayerNotSupport(status)) {
|
|
// case 1: the request is not supported by lower layer, sends down command
|
|
// some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
|
|
|
|
// send INQUIRY command if the VPD page is supported.
|
|
if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics == TRUE) {
|
|
status = ClasspDeviceGetBlockDeviceCharacteristicsVPDPage(fdoExtension, Srb);
|
|
} else {
|
|
// the INQUIRY - VPD page command to discover the info is not supported, fail the request.
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
status = IncursSeekPenalty(fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumRotationRate, &incursSeekPenalty);
|
|
}
|
|
|
|
fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus = status;
|
|
|
|
// data is ready in fdoExtension
|
|
// set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty = NotSupported;
|
|
|
|
// fill output buffer
|
|
if (NT_SUCCESS(status)) {
|
|
RtlZeroMemory(seekPenalty, length);
|
|
seekPenalty->Version = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
seekPenalty->Size = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
seekPenalty->IncursSeekPenalty = incursSeekPenalty;
|
|
information = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
|
|
|
|
} else {
|
|
information = 0;
|
|
}
|
|
|
|
} else {
|
|
// case 2: the request is supported and it completes successfully
|
|
// case 3: the request is supported by lower stack but other failure status is returned.
|
|
// from now on, the same request will be send down to lower stack directly.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty = Supported;
|
|
information = (ULONG)Irp->IoStatus.Information;
|
|
|
|
}
|
|
|
|
|
|
goto Exit;
|
|
|
|
break;
|
|
}
|
|
|
|
case NotSupported: {
|
|
status = fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus;
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
status = IncursSeekPenalty(fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumRotationRate, &incursSeekPenalty);
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
RtlZeroMemory(seekPenalty, length);
|
|
seekPenalty->Version = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
seekPenalty->Size = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
seekPenalty->IncursSeekPenalty = incursSeekPenalty;
|
|
information = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
|
|
|
|
} else {
|
|
information = 0;
|
|
}
|
|
|
|
goto Exit;
|
|
|
|
break;
|
|
}
|
|
|
|
case Supported: {
|
|
NT_ASSERT(FALSE); // this case is handled at the begining of the function.
|
|
break;
|
|
}
|
|
|
|
} // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty)
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = information;;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClasspDeviceGetLBProvisioningVPDPage(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_opt_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
USHORT pageLength = 0;
|
|
|
|
PVOID dataBuffer = NULL;
|
|
UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; // use biggest buffer possible
|
|
ULONG allocationBufferLength = bufferLength; // Since the CDB size may differ from the actual buffer allocation
|
|
PCDB cdb;
|
|
ULONG dataTransferLength = 0;
|
|
PVPD_LOGICAL_BLOCK_PROVISIONING_PAGE lbProvisioning = NULL;
|
|
|
|
//
|
|
// if the informaiton has been attempted to retrieve, return the cached status.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus != -1) {
|
|
// get cached NTSTATUS from previous call.
|
|
return fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus;
|
|
}
|
|
|
|
//
|
|
// Initialize LBProvisioningData fields to 'unsupported' defaults.
|
|
//
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.ProvisioningType = PROVISIONING_TYPE_UNKNOWN;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ = FALSE;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU = FALSE;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.ANC_SUP = FALSE;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.ThresholdExponent = 0;
|
|
|
|
//
|
|
// Try to get the Thin Provisioning VPD page (0xB2), if it is supported.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE &&
|
|
Srb != NULL)
|
|
{
|
|
#if defined(_ARM_) || defined(_ARM64_)
|
|
//
|
|
// ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
|
|
// based platforms. We are taking the conservative approach here.
|
|
//
|
|
//
|
|
allocationBufferLength = ALIGN_UP_BY(allocationBufferLength,KeGetRecommendedSharedDataAlignment());
|
|
dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength,'0CcS');
|
|
#else
|
|
dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength,'0CcS');
|
|
#endif
|
|
if (dataBuffer == NULL) {
|
|
// return without updating FdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus
|
|
// the field will remain value as "-1", so that the command will be attempted next time this function is called.
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Exit;
|
|
}
|
|
|
|
lbProvisioning = (PVPD_LOGICAL_BLOCK_PROVISIONING_PAGE)dataBuffer;
|
|
|
|
RtlZeroMemory(dataBuffer, allocationBufferLength);
|
|
|
|
if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
|
|
status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)Srb,
|
|
STORAGE_ADDRESS_TYPE_BTL8,
|
|
CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
|
|
1,
|
|
SrbExDataTypeScsiCdb16);
|
|
if (NT_SUCCESS(status)) {
|
|
((PSTORAGE_REQUEST_BLOCK)Srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
|
|
} else {
|
|
//
|
|
// Should not occur.
|
|
//
|
|
NT_ASSERT(FALSE);
|
|
}
|
|
} else {
|
|
RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
// prepare the Srb
|
|
SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);
|
|
|
|
SrbSetCdbLength(Srb, 6);
|
|
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
|
|
cdb->CDB6INQUIRY3.EnableVitalProductData = 1; //EVPD bit
|
|
cdb->CDB6INQUIRY3.PageCode = VPD_LOGICAL_BLOCK_PROVISIONING;
|
|
cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte.
|
|
|
|
status = ClassSendSrbSynchronous(fdoExtension->DeviceObject,
|
|
Srb,
|
|
dataBuffer,
|
|
allocationBufferLength,
|
|
FALSE);
|
|
|
|
dataTransferLength = SrbGetDataTransferLength(Srb);
|
|
}
|
|
|
|
//
|
|
// Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
|
|
// buffer was larger than necessary.
|
|
//
|
|
if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength)
|
|
{
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
REVERSE_BYTES_SHORT(&pageLength, &(lbProvisioning->PageLength));
|
|
}
|
|
|
|
if ( NT_SUCCESS(status) &&
|
|
((dataTransferLength < 0x08) ||
|
|
(pageLength < (FIELD_OFFSET(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE, Reserved2) - FIELD_OFFSET(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE,ThresholdExponent))) ||
|
|
(lbProvisioning->PageCode != VPD_LOGICAL_BLOCK_PROVISIONING)) ) {
|
|
// the device should return at least 8 bytes of data for use.
|
|
// 'PageCode' shall match and we need all the relevant data after the header.
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
}
|
|
|
|
//
|
|
// Fill in the FDO extension with either the data from the VPD page, or
|
|
// use defaults if there was an error.
|
|
//
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.ProvisioningType = lbProvisioning->ProvisioningType;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ = lbProvisioning->LBPRZ;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU = lbProvisioning->LBPU;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.ANC_SUP = lbProvisioning->ANC_SUP;
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.ThresholdExponent = lbProvisioning->ThresholdExponent;
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_PNP,
|
|
"ClasspDeviceGetLBProvisioningVPDPage (%p): %s %s (rev %s) reported following parameters: \
|
|
\n\t\t\tProvisioningType: %u \
|
|
\n\t\t\tLBPRZ: %u \
|
|
\n\t\t\tLBPU: %u \
|
|
\n\t\t\tANC_SUP: %I64u \
|
|
\n\t\t\tThresholdExponent: %u\n",
|
|
DeviceObject,
|
|
(PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->VendorIdOffset),
|
|
(PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductIdOffset),
|
|
(PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductRevisionOffset),
|
|
lbProvisioning->ProvisioningType,
|
|
lbProvisioning->LBPRZ,
|
|
lbProvisioning->LBPU,
|
|
lbProvisioning->ANC_SUP,
|
|
lbProvisioning->ThresholdExponent));
|
|
}
|
|
} else {
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = status;
|
|
|
|
Exit:
|
|
FREE_POOL(dataBuffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS ClasspDeviceGetBlockLimitsVPDPage(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_Inout_bytecount_(SrbSize) PSCSI_REQUEST_BLOCK Srb,
|
|
_In_ ULONG SrbSize,
|
|
_Out_ PCLASS_VPD_B0_DATA BlockLimitsData
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PVOID dataBuffer = NULL;
|
|
UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; // use biggest buffer possible
|
|
ULONG allocationBufferLength = bufferLength;
|
|
PCDB cdb;
|
|
PVPD_BLOCK_LIMITS_PAGE blockLimits = NULL;
|
|
ULONG dataTransferLength = 0;
|
|
|
|
//
|
|
// Set default values for UNMAP parameters based upon UNMAP support or lack
|
|
// thereof.
|
|
//
|
|
if (FdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU) {
|
|
//
|
|
// If UNMAP is supported, we default to the maximum LBA count and
|
|
// block descriptor count. We also default the UNMAP granularity to
|
|
// a single block and specify no granularity alignment.
|
|
//
|
|
BlockLimitsData->MaxUnmapLbaCount = (ULONG)-1;
|
|
BlockLimitsData->MaxUnmapBlockDescrCount = (ULONG)-1;
|
|
BlockLimitsData->OptimalUnmapGranularity = 1;
|
|
BlockLimitsData->UnmapGranularityAlignment = 0;
|
|
BlockLimitsData->UGAVALID = FALSE;
|
|
} else {
|
|
BlockLimitsData->MaxUnmapLbaCount = 0;
|
|
BlockLimitsData->MaxUnmapBlockDescrCount = 0;
|
|
BlockLimitsData->OptimalUnmapGranularity = 0;
|
|
BlockLimitsData->UnmapGranularityAlignment = 0;
|
|
BlockLimitsData->UGAVALID = FALSE;
|
|
}
|
|
|
|
//
|
|
// Try to get the Block Limits VPD page (0xB0), if it is supported.
|
|
//
|
|
if (FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE)
|
|
{
|
|
#if defined(_ARM_) || defined(_ARM64_)
|
|
//
|
|
// ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
|
|
// based platforms. We are taking the conservative approach here.
|
|
//
|
|
allocationBufferLength = ALIGN_UP_BY(allocationBufferLength, KeGetRecommendedSharedDataAlignment());
|
|
dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength, '0CcS');
|
|
#else
|
|
dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, '0CcS');
|
|
#endif
|
|
if (dataBuffer == NULL)
|
|
{
|
|
// return without updating FdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus
|
|
// the field will remain value as "-1", so that the command will be attempted next time this function is called.
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Exit;
|
|
}
|
|
|
|
blockLimits = (PVPD_BLOCK_LIMITS_PAGE)dataBuffer;
|
|
|
|
RtlZeroMemory(dataBuffer, allocationBufferLength);
|
|
|
|
if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded")
|
|
#endif
|
|
status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)Srb,
|
|
STORAGE_ADDRESS_TYPE_BTL8,
|
|
SrbSize,
|
|
1,
|
|
SrbExDataTypeScsiCdb16);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
((PSTORAGE_REQUEST_BLOCK)Srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
|
|
} else {
|
|
//
|
|
// Should not occur.
|
|
//
|
|
NT_ASSERT(FALSE);
|
|
}
|
|
} else {
|
|
RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
// prepare the Srb
|
|
SrbSetTimeOutValue(Srb, FdoExtension->TimeOutValue);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
SrbAssignSrbFlags(Srb, FdoExtension->SrbFlags);
|
|
|
|
SrbSetCdbLength(Srb, 6);
|
|
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
|
|
cdb->CDB6INQUIRY3.EnableVitalProductData = 1; //EVPD bit
|
|
cdb->CDB6INQUIRY3.PageCode = VPD_BLOCK_LIMITS;
|
|
cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte.
|
|
|
|
status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
|
|
Srb,
|
|
dataBuffer,
|
|
allocationBufferLength,
|
|
FALSE);
|
|
dataTransferLength = SrbGetDataTransferLength(Srb);
|
|
}
|
|
|
|
//
|
|
// Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
|
|
// buffer was larger than necessary.
|
|
//
|
|
|
|
if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength)
|
|
{
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
USHORT pageLength;
|
|
REVERSE_BYTES_SHORT(&pageLength, &(blockLimits->PageLength));
|
|
|
|
//
|
|
// Regardless of the device's support for unmap, cache away at least the basic block limits information
|
|
//
|
|
if (dataTransferLength >= 0x10 && blockLimits->PageCode == VPD_BLOCK_LIMITS) {
|
|
|
|
// (6:7) OPTIMAL TRANSFER LENGTH GRANULARITY
|
|
REVERSE_BYTES_SHORT(&BlockLimitsData->OptimalTransferLengthGranularity, &blockLimits->OptimalTransferLengthGranularity);
|
|
// (8:11) MAXIMUM TRANSFER LENGTH
|
|
REVERSE_BYTES(&BlockLimitsData->MaximumTransferLength, &blockLimits->MaximumTransferLength);
|
|
// (12:15) OPTIMAL TRANSFER LENGTH
|
|
REVERSE_BYTES(&BlockLimitsData->OptimalTransferLength, &blockLimits->OptimalTransferLength);
|
|
}
|
|
|
|
if ((dataTransferLength < 0x24) ||
|
|
(pageLength < (FIELD_OFFSET(VPD_BLOCK_LIMITS_PAGE,Reserved1) - FIELD_OFFSET(VPD_BLOCK_LIMITS_PAGE,Reserved0))) ||
|
|
(blockLimits->PageCode != VPD_BLOCK_LIMITS))
|
|
{
|
|
// the device should return at least 36 bytes of data for use.
|
|
// 'PageCode' shall match and we need all the relevant data after the header.
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
// cache data into FdoExtension
|
|
// (20:23) MAXIMUM UNMAP LBA COUNT
|
|
REVERSE_BYTES(&BlockLimitsData->MaxUnmapLbaCount, &blockLimits->MaximumUnmapLBACount);
|
|
// (24:27) MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT
|
|
REVERSE_BYTES(&BlockLimitsData->MaxUnmapBlockDescrCount, &blockLimits->MaximumUnmapBlockDescriptorCount);
|
|
// (28:31) OPTIMAL UNMAP GRANULARITY
|
|
REVERSE_BYTES(&BlockLimitsData->OptimalUnmapGranularity, &blockLimits->OptimalUnmapGranularity);
|
|
|
|
// (32:35) UNMAP GRANULARITY ALIGNMENT; (32) bit7: UGAVALID
|
|
BlockLimitsData->UGAVALID = blockLimits->UGAValid;
|
|
if (BlockLimitsData->UGAVALID == TRUE) {
|
|
REVERSE_BYTES(&BlockLimitsData->UnmapGranularityAlignment, &blockLimits->UnmapGranularityAlignment);
|
|
BlockLimitsData->UnmapGranularityAlignment &= 0x7FFFFFFF; // remove value of UGAVALID bit.
|
|
} else {
|
|
BlockLimitsData->UnmapGranularityAlignment = 0;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_PNP,
|
|
"ClasspDeviceGetBlockLimitsVPDPage (%p): %s %s (rev %s) reported following parameters: \
|
|
\n\t\t\tOptimalTransferLengthGranularity: %u \
|
|
\n\t\t\tMaximumTransferLength: %u \
|
|
\n\t\t\tOptimalTransferLength: %u \
|
|
\n\t\t\tMaximumUnmapLBACount: %u \
|
|
\n\t\t\tMaximumUnmapBlockDescriptorCount: %u \
|
|
\n\t\t\tOptimalUnmapGranularity: %u \
|
|
\n\t\t\tUGAValid: %u \
|
|
\n\t\t\tUnmapGranularityAlignment: %u\n",
|
|
FdoExtension->DeviceObject,
|
|
(PCSZ)(((PUCHAR)FdoExtension->DeviceDescriptor) + FdoExtension->DeviceDescriptor->VendorIdOffset),
|
|
(PCSZ)(((PUCHAR)FdoExtension->DeviceDescriptor) + FdoExtension->DeviceDescriptor->ProductIdOffset),
|
|
(PCSZ)(((PUCHAR)FdoExtension->DeviceDescriptor) + FdoExtension->DeviceDescriptor->ProductRevisionOffset),
|
|
BlockLimitsData->OptimalTransferLengthGranularity,
|
|
BlockLimitsData->MaximumTransferLength,
|
|
BlockLimitsData->OptimalTransferLength,
|
|
BlockLimitsData->MaxUnmapLbaCount,
|
|
BlockLimitsData->MaxUnmapBlockDescrCount,
|
|
BlockLimitsData->OptimalUnmapGranularity,
|
|
BlockLimitsData->UGAVALID,
|
|
BlockLimitsData->UnmapGranularityAlignment));
|
|
|
|
}
|
|
} else {
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
BlockLimitsData->CommandStatus = status;
|
|
|
|
Exit:
|
|
FREE_POOL(dataBuffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS ClasspDeviceTrimProperty(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
|
|
If it's not supported, INQUIRY (Block Limits VPD page) will be sent down to retrieve the information.
|
|
*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
ULONG length = 0;
|
|
ULONG information = 0;
|
|
|
|
PDEVICE_TRIM_DESCRIPTOR trimDescr;
|
|
|
|
UNREFERENCED_PARAMETER(Srb);
|
|
|
|
if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
|
|
(TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
|
|
(fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty == Supported) ) {
|
|
// if it's not disk, forward the request to lower layer,
|
|
// if the IOCTL is supported by lower stack, forward it down.
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
} else if (query->QueryType != PropertyStandardQuery) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Request validation.
|
|
// Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto Exit;
|
|
}
|
|
|
|
// do not touch this buffer because it can still be used as input buffer for lower layer in 'SupportUnknown' case.
|
|
trimDescr = (PDEVICE_TRIM_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
if (length < sizeof(DEVICE_TRIM_DESCRIPTOR)) {
|
|
|
|
if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
|
|
information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
trimDescr->Version = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
trimDescr->Size = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
}
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// note that 'Supported' case has been handled at the beginning of this function.
|
|
//
|
|
switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty) {
|
|
case SupportUnknown: {
|
|
// send down request and wait for the request to complete.
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
|
|
if ( (status == STATUS_NOT_SUPPORTED) ||
|
|
(status == STATUS_NOT_IMPLEMENTED) ||
|
|
(status == STATUS_INVALID_DEVICE_REQUEST) ||
|
|
(status == STATUS_INVALID_PARAMETER_1) ) {
|
|
// case 1: the request is not supported by lower layer, sends down command
|
|
// some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
|
|
status = fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus;
|
|
NT_ASSERT(status != -1);
|
|
|
|
// data is ready in fdoExtension
|
|
// set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty = NotSupported;
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
// fill output buffer
|
|
RtlZeroMemory(trimDescr, length);
|
|
trimDescr->Version = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
trimDescr->Size = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
trimDescr->TrimEnabled = ClasspSupportsUnmap(fdoExtension->FunctionSupportInfo);
|
|
|
|
// set returned data length
|
|
information = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
} else {
|
|
// there was error retrieving TrimProperty. Surface the error up from 'status' variable.
|
|
information = 0;
|
|
}
|
|
goto Exit;
|
|
} else {
|
|
// case 2: the request is supported and it completes successfully
|
|
// case 3: the request is supported by lower stack but other failure status is returned.
|
|
// from now on, the same request will be send down to lower stack directly.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty = Supported;
|
|
information = (ULONG)Irp->IoStatus.Information;
|
|
goto Exit;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NotSupported: {
|
|
status = fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus;
|
|
NT_ASSERT(status != -1);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
RtlZeroMemory(trimDescr, length);
|
|
trimDescr->Version = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
trimDescr->Size = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
trimDescr->TrimEnabled = ClasspSupportsUnmap(fdoExtension->FunctionSupportInfo);
|
|
|
|
information = sizeof(DEVICE_TRIM_DESCRIPTOR);
|
|
} else {
|
|
information = 0;
|
|
}
|
|
goto Exit;
|
|
|
|
break;
|
|
}
|
|
|
|
case Supported: {
|
|
NT_ASSERT(FALSE); // this case is handled at the begining of the function.
|
|
break;
|
|
}
|
|
|
|
} // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty)
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = information;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClasspDeviceLBProvisioningProperty(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
NTSTATUS blockLimitsStatus;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
ULONG length = 0;
|
|
ULONG information = 0;
|
|
CLASS_VPD_B0_DATA blockLimitsData;
|
|
ULONG generationCount;
|
|
|
|
PDEVICE_LB_PROVISIONING_DESCRIPTOR lbpDescr;
|
|
|
|
UNREFERENCED_PARAMETER(Srb);
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
} else if (query->QueryType != PropertyStandardQuery) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Request validation.
|
|
// Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto Exit;
|
|
}
|
|
|
|
lbpDescr = (PDEVICE_LB_PROVISIONING_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
RtlZeroMemory(lbpDescr, length);
|
|
|
|
if (length < DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE) {
|
|
|
|
if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
|
|
information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
lbpDescr->Version = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
|
|
lbpDescr->Size = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
|
|
status = STATUS_SUCCESS;
|
|
goto Exit;
|
|
}
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Set the structure version/size based upon the size of the given output
|
|
// buffer. We may be working with an older component that was built with
|
|
// the V1 structure definition.
|
|
//
|
|
if (length < sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR)) {
|
|
lbpDescr->Version = DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE;
|
|
lbpDescr->Size = DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE;
|
|
information = DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE;
|
|
} else {
|
|
lbpDescr->Version = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
|
|
lbpDescr->Size = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
|
|
information = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
|
|
}
|
|
|
|
//
|
|
// Take a snapshot of the block limits data since it can change.
|
|
// If we failed to get the block limits data, we'll just set the Optimal
|
|
// Unmap Granularity (and alignment) will default to 0. We don't want to
|
|
// fail the request outright since there is some non-block limits data that
|
|
// we can return.
|
|
//
|
|
blockLimitsStatus = ClasspBlockLimitsDataSnapshot(fdoExtension,
|
|
TRUE,
|
|
&blockLimitsData,
|
|
&generationCount);
|
|
|
|
//
|
|
// Fill in the output buffer. All data is copied from the FDO extension where we
|
|
// cached Logical Block Provisioning info when the device was first initialized.
|
|
//
|
|
|
|
lbpDescr->ThinProvisioningEnabled = ClasspIsThinProvisioned(fdoExtension->FunctionSupportInfo);
|
|
|
|
//
|
|
// Make sure we have a non-zero value for the number of bytes per block.
|
|
//
|
|
if (fdoExtension->DiskGeometry.BytesPerSector == 0)
|
|
{
|
|
status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
|
|
if(!NT_SUCCESS(status) || fdoExtension->DiskGeometry.BytesPerSector == 0)
|
|
{
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
information = 0;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
lbpDescr->ThinProvisioningReadZeros = fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ;
|
|
lbpDescr->AnchorSupported = fdoExtension->FunctionSupportInfo->LBProvisioningData.ANC_SUP;
|
|
|
|
if (NT_SUCCESS(blockLimitsStatus)) {
|
|
lbpDescr->UnmapGranularityAlignmentValid = blockLimitsData.UGAVALID;
|
|
|
|
//
|
|
// Granularity and Alignment are given to us in units of blocks,
|
|
// but we convert and return them in bytes as it is more convenient
|
|
// to the caller.
|
|
//
|
|
lbpDescr->OptimalUnmapGranularity = (ULONGLONG)blockLimitsData.OptimalUnmapGranularity * fdoExtension->DiskGeometry.BytesPerSector;
|
|
lbpDescr->UnmapGranularityAlignment = (ULONGLONG)blockLimitsData.UnmapGranularityAlignment * fdoExtension->DiskGeometry.BytesPerSector;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
|
|
//
|
|
// If the output buffer is large enough (i.e. not a V1 structure) copy
|
|
// over the max UNMAP LBA count and max UNMAP block descriptor count.
|
|
//
|
|
if (length >= sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR)) {
|
|
lbpDescr->MaxUnmapLbaCount = blockLimitsData.MaxUnmapLbaCount;
|
|
lbpDescr->MaxUnmapBlockDescriptorCount = blockLimitsData.MaxUnmapBlockDescrCount;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = information;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
VOID
|
|
ConvertDataSetRangeToUnmapBlockDescr(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_In_ PUNMAP_BLOCK_DESCRIPTOR BlockDescr,
|
|
_Inout_ PULONG CurrentBlockDescrIndex,
|
|
_In_ ULONG MaxBlockDescrIndex,
|
|
_Inout_ PULONGLONG CurrentLbaCount,
|
|
_In_ ULONGLONG MaxLbaCount,
|
|
_Inout_ PDEVICE_DATA_SET_RANGE DataSetRange
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert DEVICE_DATA_SET_RANGE entry to be UNMAP_BLOCK_DESCRIPTOR entries.
|
|
|
|
As LengthInBytes field in DEVICE_DATA_SET_RANGE structure is 64 bits (bytes)
|
|
and LbaCount field in UNMAP_BLOCK_DESCRIPTOR structure is 32 bits (sectors),
|
|
it's possible that one DEVICE_DATA_SET_RANGE entry needs multiple UNMAP_BLOCK_DESCRIPTOR entries.
|
|
We must also take the unmap granularity into consideration and split up the
|
|
the given ranges so that they are aligned with the specified granularity.
|
|
|
|
Arguments:
|
|
All arguments must be validated by the caller.
|
|
|
|
FdoExtension - The FDO extension of the device to which the unmap
|
|
command that will use the resulting unmap block descriptors will be
|
|
sent.
|
|
BlockDescr - Pointer to a buffer that will contain the unmap block
|
|
descriptors. This buffer should be allocated by the caller and the
|
|
caller should also ensure that it is large enough to contain all the
|
|
requested descriptors. Its size is implied by MaxBlockDescrIndex.
|
|
CurrentBlockDescrIndex - This contains the next block descriptor index to
|
|
be processed when this function returns. This function should be called
|
|
again with the same parameter to continue processing.
|
|
MaxBlockDescrIndex - This is the index of the last unmap block descriptor,
|
|
provided so that the function does not go off the end of BlockDescr.
|
|
CurrentLbaCount - This contains the number of LBAs left to be processed
|
|
when this function returns. This function should be called again with
|
|
the same parameter to continue processing.
|
|
MaxLbaCount - This is the max number of LBAs that can be sent in a single
|
|
unmap command.
|
|
DataSetRange - This range will be modified to reflect the un-converted part.
|
|
It must be valid (including being granularity-aligned) when it is first
|
|
passed to this function.
|
|
|
|
Return Value:
|
|
|
|
Count of UNMAP_BLOCK_DESCRIPTOR entries converted.
|
|
|
|
NOTE: if LengthInBytes does not reach to 0, the conversion for DEVICE_DATA_SET_RANGE entry
|
|
is not completed. Further conversion is needed by calling this function again.
|
|
|
|
--*/
|
|
{
|
|
|
|
ULONGLONG startingSector;
|
|
ULONGLONG sectorCount;
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"ConvertDataSetRangeToUnmapBlockDescr (%p): Generating UNMAP Block Descriptors from DataSetRange: \
|
|
\n\t\tStartingOffset = %I64u bytes \
|
|
\n\t\tLength = %I64u bytes\n",
|
|
FdoExtension->DeviceObject,
|
|
DataSetRange->StartingOffset,
|
|
DataSetRange->LengthInBytes));
|
|
|
|
while ( (DataSetRange->LengthInBytes > 0) &&
|
|
(*CurrentBlockDescrIndex < MaxBlockDescrIndex) &&
|
|
(*CurrentLbaCount < MaxLbaCount) ) {
|
|
|
|
//
|
|
// Convert the starting offset and length from bytes to blocks.
|
|
//
|
|
startingSector = (ULONGLONG)(DataSetRange->StartingOffset / FdoExtension->DiskGeometry.BytesPerSector);
|
|
sectorCount = (DataSetRange->LengthInBytes / FdoExtension->DiskGeometry.BytesPerSector);
|
|
|
|
//
|
|
// Make sure the sector count isn't more than can be specified with a
|
|
// single descriptor.
|
|
//
|
|
if (sectorCount > MAXULONG) {
|
|
sectorCount = MAXULONG;
|
|
}
|
|
|
|
//
|
|
// The max LBA count is the max number of LBAs that can be unmapped with
|
|
// a single UNMAP command. Make sure we don't exceed this value.
|
|
//
|
|
if ((*CurrentLbaCount + sectorCount) > MaxLbaCount) {
|
|
sectorCount = MaxLbaCount - *CurrentLbaCount;
|
|
}
|
|
|
|
REVERSE_BYTES_QUAD(BlockDescr[*CurrentBlockDescrIndex].StartingLba, &startingSector);
|
|
REVERSE_BYTES(BlockDescr[*CurrentBlockDescrIndex].LbaCount, (PULONG)§orCount);
|
|
|
|
DataSetRange->StartingOffset += sectorCount * FdoExtension->DiskGeometry.BytesPerSector;
|
|
DataSetRange->LengthInBytes -= sectorCount * FdoExtension->DiskGeometry.BytesPerSector;
|
|
|
|
*CurrentBlockDescrIndex += 1;
|
|
*CurrentLbaCount += (ULONG)sectorCount;
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"ConvertDataSetRangeToUnmapBlockDescr (%p): Generated UNMAP Block Descriptor: \
|
|
\n\t\t\tStartingLBA = %I64u \
|
|
\n\t\t\tLBACount = %I64u\n",
|
|
FdoExtension->DeviceObject,
|
|
startingSector,
|
|
sectorCount));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
DeviceProcessDsmTrimRequest(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_In_ PDEVICE_DATA_SET_RANGE DataSetRanges,
|
|
_In_ ULONG DataSetRangesCount,
|
|
_In_ ULONG UnmapGranularity,
|
|
_In_ ULONG SrbFlags,
|
|
_In_ PIRP Irp,
|
|
_In_ PGUID ActivityId,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process TRIM request that received from upper layer.
|
|
|
|
Arguments:
|
|
|
|
FdoExtension
|
|
DataSetRanges - this parameter must be already validated in caller.
|
|
DataSetRangesCount - this parameter must be already validated in caller.
|
|
UnmapGranularity - The unmap granularity in blocks. This is used to split
|
|
up the unmap command into chunks that are granularity-aligned.
|
|
Srb - The SRB to use for the unmap command. The caller must allocate it,
|
|
but this function will take care of initialzing it.
|
|
|
|
Return Value:
|
|
|
|
status of the operation
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PUNMAP_LIST_HEADER buffer = NULL;
|
|
PUNMAP_BLOCK_DESCRIPTOR blockDescrPointer;
|
|
ULONG bufferLength;
|
|
ULONG maxBlockDescrCount;
|
|
ULONG neededBlockDescrCount;
|
|
ULONG i;
|
|
|
|
BOOLEAN allDataSetRangeFullyConverted;
|
|
BOOLEAN needToSendCommand;
|
|
BOOLEAN tempDataSetRangeFullyConverted;
|
|
|
|
ULONG dataSetRangeIndex;
|
|
DEVICE_DATA_SET_RANGE tempDataSetRange;
|
|
|
|
ULONG blockDescrIndex;
|
|
ULONGLONG lbaCount;
|
|
ULONGLONG maxLbaCount;
|
|
ULONGLONG maxParameterListLength;
|
|
|
|
|
|
UNREFERENCED_PARAMETER(UnmapGranularity);
|
|
UNREFERENCED_PARAMETER(ActivityId);
|
|
UNREFERENCED_PARAMETER(Irp);
|
|
|
|
//
|
|
// The given LBA ranges are in DEVICE_DATA_SET_RANGE format and need to be converted into UNMAP Block Descriptors.
|
|
// The UNMAP command is able to carry 0xFFFF bytes (0xFFF8 in reality as there are 8 bytes of header plus n*16 bytes of Block Descriptors) of data.
|
|
// The actual size will also be constrained by the Maximum LBA Count and Maximum Transfer Length.
|
|
//
|
|
|
|
//
|
|
// 1.1 Calculate how many Block Descriptors are needed to complete this request.
|
|
//
|
|
neededBlockDescrCount = 0;
|
|
for (i = 0; i < DataSetRangesCount; i++) {
|
|
lbaCount = DataSetRanges[i].LengthInBytes / FdoExtension->DiskGeometry.BytesPerSector;
|
|
|
|
//
|
|
// 1.1.1 the UNMAP_BLOCK_DESCRIPTOR LbaCount is 32 bits, the max value is 0xFFFFFFFF
|
|
//
|
|
if (lbaCount > 0) {
|
|
neededBlockDescrCount += (ULONG)((lbaCount - 1) / MAXULONG + 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Honor Max Unmap Block Descriptor Count if it has been specified. Otherwise,
|
|
// use the maximum value that the Parameter List Length field will allow (0xFFFF).
|
|
// If the count is 0xFFFFFFFF, then no maximum is specified.
|
|
//
|
|
if (FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapBlockDescrCount != 0 &&
|
|
FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapBlockDescrCount != MAXULONG)
|
|
{
|
|
maxParameterListLength = (ULONGLONG)(FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapBlockDescrCount * sizeof(UNMAP_BLOCK_DESCRIPTOR))
|
|
+ sizeof(UNMAP_LIST_HEADER);
|
|
|
|
//
|
|
// In the SBC-3, the Max Unmap Block Descriptor Count field in the 0xB0
|
|
// page is 4 bytes and the Parameter List Length in the UNMAP command is
|
|
// 2 bytes, therefore it is possible that the Max Unmap Block Descriptor
|
|
// Count could imply more bytes than can be specified in the Parameter
|
|
// List Length field. Adjust for that here.
|
|
//
|
|
maxParameterListLength = min(maxParameterListLength, MAXUSHORT);
|
|
}
|
|
else
|
|
{
|
|
maxParameterListLength = MAXUSHORT;
|
|
}
|
|
|
|
//
|
|
// 1.2 Calculate the buffer size needed, capped by the device's limitations.
|
|
//
|
|
bufferLength = min(FdoExtension->PrivateFdoData->HwMaxXferLen, (ULONG)maxParameterListLength);
|
|
bufferLength = min(bufferLength, (neededBlockDescrCount * sizeof(UNMAP_BLOCK_DESCRIPTOR) + sizeof(UNMAP_LIST_HEADER)));
|
|
|
|
maxBlockDescrCount = (bufferLength - sizeof(UNMAP_LIST_HEADER)) / sizeof(UNMAP_BLOCK_DESCRIPTOR);
|
|
|
|
if (maxBlockDescrCount == 0) {
|
|
//
|
|
// This shouldn't happen since we've already done validation.
|
|
//
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"DeviceProcessDsmTrimRequest (%p): Max Block Descriptor count is Zero\n",
|
|
FdoExtension->DeviceObject));
|
|
|
|
NT_ASSERT(maxBlockDescrCount != 0);
|
|
status = STATUS_DATA_ERROR;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// The Maximum LBA Count is set during device initialization.
|
|
//
|
|
maxLbaCount = (ULONGLONG)FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapLbaCount;
|
|
if (maxLbaCount == 0) {
|
|
//
|
|
// This shouldn't happen since we've already done validation.
|
|
//
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"DeviceProcessDsmTrimRequest (%p): Max LBA count is Zero\n",
|
|
FdoExtension->DeviceObject));
|
|
|
|
NT_ASSERT(maxLbaCount != 0);
|
|
status = STATUS_DATA_ERROR;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Finally, allocate the buffer we'll use to send the UNMAP command.
|
|
//
|
|
|
|
#if defined(_ARM_) || defined(_ARM64_)
|
|
//
|
|
// ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
|
|
// based platforms. We are taking the conservative approach here.
|
|
//
|
|
bufferLength = ALIGN_UP_BY(bufferLength,KeGetRecommendedSharedDataAlignment());
|
|
buffer = (PUNMAP_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, bufferLength, CLASS_TAG_LB_PROVISIONING);
|
|
#else
|
|
buffer = (PUNMAP_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, CLASS_TAG_LB_PROVISIONING);
|
|
#endif
|
|
|
|
if (buffer == NULL) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Exit;
|
|
}
|
|
|
|
RtlZeroMemory(buffer, bufferLength);
|
|
|
|
blockDescrPointer = &buffer->Descriptors[0];
|
|
|
|
allDataSetRangeFullyConverted = FALSE;
|
|
needToSendCommand = FALSE;
|
|
tempDataSetRangeFullyConverted = TRUE;
|
|
dataSetRangeIndex = 0;
|
|
RtlZeroMemory(&tempDataSetRange, sizeof(tempDataSetRange));
|
|
|
|
blockDescrIndex = 0;
|
|
lbaCount = 0;
|
|
|
|
|
|
while (!allDataSetRangeFullyConverted) {
|
|
|
|
//
|
|
// If the previous entry conversion completed, go on to the next one;
|
|
// otherwise, continue processing the current entry.
|
|
//
|
|
if (tempDataSetRangeFullyConverted) {
|
|
tempDataSetRange.StartingOffset = DataSetRanges[dataSetRangeIndex].StartingOffset;
|
|
tempDataSetRange.LengthInBytes = DataSetRanges[dataSetRangeIndex].LengthInBytes;
|
|
dataSetRangeIndex++;
|
|
}
|
|
|
|
ConvertDataSetRangeToUnmapBlockDescr(FdoExtension,
|
|
blockDescrPointer,
|
|
&blockDescrIndex,
|
|
maxBlockDescrCount,
|
|
&lbaCount,
|
|
maxLbaCount,
|
|
&tempDataSetRange
|
|
);
|
|
|
|
tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE;
|
|
|
|
allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && (dataSetRangeIndex == DataSetRangesCount);
|
|
|
|
//
|
|
// Send the UNMAP command when the buffer is full or when all input entries are converted.
|
|
//
|
|
if ((blockDescrIndex == maxBlockDescrCount) || // Buffer full or block descriptor count reached
|
|
(lbaCount == maxLbaCount) || // Block LBA count reached
|
|
allDataSetRangeFullyConverted) { // All DataSetRanges have been converted
|
|
|
|
USHORT transferSize;
|
|
USHORT tempSize;
|
|
PCDB cdb;
|
|
|
|
//
|
|
// Get the transfer size, including the header.
|
|
//
|
|
transferSize = (USHORT)(blockDescrIndex * sizeof(UNMAP_BLOCK_DESCRIPTOR) + sizeof(UNMAP_LIST_HEADER));
|
|
if (transferSize > bufferLength)
|
|
{
|
|
//
|
|
// This should never happen.
|
|
//
|
|
NT_ASSERT(transferSize <= bufferLength);
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
break;
|
|
}
|
|
|
|
tempSize = transferSize - (USHORT)FIELD_OFFSET(UNMAP_LIST_HEADER, BlockDescrDataLength);
|
|
REVERSE_BYTES_SHORT(buffer->DataLength, &tempSize);
|
|
tempSize = transferSize - (USHORT)FIELD_OFFSET(UNMAP_LIST_HEADER, Descriptors[0]);
|
|
REVERSE_BYTES_SHORT(buffer->BlockDescrDataLength, &tempSize);
|
|
|
|
//
|
|
// Initialize the SRB.
|
|
//
|
|
if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
|
|
status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)Srb,
|
|
STORAGE_ADDRESS_TYPE_BTL8,
|
|
CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
|
|
1,
|
|
SrbExDataTypeScsiCdb16);
|
|
if (NT_SUCCESS(status)) {
|
|
((PSTORAGE_REQUEST_BLOCK)Srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
|
|
} else {
|
|
//
|
|
// Should not occur.
|
|
//
|
|
NT_ASSERT(FALSE);
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
}
|
|
|
|
//
|
|
// Prepare the Srb
|
|
//
|
|
SrbSetTimeOutValue(Srb, FdoExtension->TimeOutValue);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
|
|
//
|
|
// Set the SrbFlags to indicate that it's a data-out operation.
|
|
// Also set any passed-in SrbFlags.
|
|
//
|
|
SrbAssignSrbFlags(Srb, FdoExtension->SrbFlags);
|
|
SrbClearSrbFlags(Srb, SRB_FLAGS_DATA_IN);
|
|
SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_OUT);
|
|
SrbSetSrbFlags(Srb, SrbFlags);
|
|
|
|
SrbSetCdbLength(Srb, 10);
|
|
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->UNMAP.OperationCode = SCSIOP_UNMAP;
|
|
cdb->UNMAP.Anchor = 0;
|
|
cdb->UNMAP.GroupNumber = 0;
|
|
cdb->UNMAP.AllocationLength[0] = (UCHAR)(transferSize >> 8);
|
|
cdb->UNMAP.AllocationLength[1] = (UCHAR)transferSize;
|
|
|
|
status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
|
|
Srb,
|
|
buffer,
|
|
transferSize,
|
|
TRUE);
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"DeviceProcessDsmTrimRequest (%p): UNMAP command issued. Returned NTSTATUS: %!STATUS!.\n",
|
|
FdoExtension->DeviceObject,
|
|
status
|
|
));
|
|
|
|
//
|
|
// Clear the buffer so we can re-use it.
|
|
//
|
|
blockDescrIndex = 0;
|
|
lbaCount = 0;
|
|
RtlZeroMemory(buffer, bufferLength);
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
FREE_POOL(buffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClasspDeviceTrimProcess(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_In_ PGUID ActivityId,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
This function is to process IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES with DeviceDsmAction_Trim.
|
|
At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
|
|
If it's not supported, UNMAP (with anchor attribute set) will be sent down to process the request.
|
|
*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
|
|
PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
PDEVICE_DATA_SET_RANGE dataSetRanges;
|
|
ULONG dataSetRangesCount;
|
|
DEVICE_DATA_SET_RANGE entireDataSetRange = {0};
|
|
ULONG i;
|
|
ULONGLONG granularityAlignmentInBytes;
|
|
ULONG granularityInBlocks;
|
|
ULONG srbFlags = 0;
|
|
|
|
CLASS_VPD_B0_DATA blockLimitsData;
|
|
ULONG generationCount;
|
|
|
|
|
|
if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
|
|
(TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
|
|
(fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess == Supported) ) {
|
|
// if it's not disk, forward the request to lower layer,
|
|
// if the IOCTL is supported by lower stack, forward it down.
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClasspDeviceTrimProcess (%p): Lower layer supports Trim DSM IOCTL, forwarding IOCTL.\n",
|
|
DeviceObject));
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Request validation.
|
|
// Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
//
|
|
// If the caller has not set the "entire dataset range" flag then at least
|
|
// one dataset range should be specified. However, if the caller *has* set
|
|
// the flag, then there should not be any dataset ranges specified.
|
|
//
|
|
if ((!TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE) &&
|
|
(dsmAttributes->DataSetRangesOffset == 0 ||
|
|
dsmAttributes->DataSetRangesLength == 0)) ||
|
|
(TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE) &&
|
|
(dsmAttributes->DataSetRangesOffset != 0 ||
|
|
dsmAttributes->DataSetRangesLength != 0))) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// note that 'Supported' case has been handled at the beginning of this function.
|
|
//
|
|
switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess) {
|
|
case SupportUnknown: {
|
|
// send down request and wait for the request to complete.
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClasspDeviceTrimProcess (%p): Trim DSM IOCTL support unknown. Forwarded IOCTL and received NTSTATUS %!STATUS!.\n",
|
|
DeviceObject,
|
|
status));
|
|
|
|
if (ClasspLowerLayerNotSupport(status)) {
|
|
// case 1: the request is not supported by lower layer, sends down command
|
|
// some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
|
|
// In this case we'll just fall through to the NotSupported case so that we can handle it ourselves.
|
|
|
|
//
|
|
// VPD pages 0xB2 and 0xB0 should have been cached in Start Device phase - ClassPnpStartDevice.
|
|
// 0xB2 page: fdoExtension->FunctionSupportInfo->LBProvisioningData;
|
|
// 0xB0 page: fdoExtension->FunctionSupportInfo->BlockLimitsData
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE) {
|
|
NT_ASSERT(fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus != -1);
|
|
}
|
|
|
|
if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE) {
|
|
NT_ASSERT(fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus != -1);
|
|
}
|
|
|
|
} else {
|
|
|
|
// case 2: the request is supported and it completes successfully
|
|
// case 3: the request is supported by lower stack but other failure status is returned.
|
|
// from now on, the same request will be send down to lower stack directly.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess = Supported;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
case NotSupported: {
|
|
|
|
// send UNMAP command if it is supported. don't need to check 'status' value.
|
|
if (ClasspSupportsUnmap(fdoExtension->FunctionSupportInfo))
|
|
{
|
|
//
|
|
// Make sure that we know the bytes per sector (logical block) as it's
|
|
// necessary for calculations involving granularity and alignment.
|
|
//
|
|
if (fdoExtension->DiskGeometry.BytesPerSector == 0) {
|
|
status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
|
|
if(!NT_SUCCESS(status) || fdoExtension->DiskGeometry.BytesPerSector == 0) {
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Take a snapshot of the block limits data since it can change.
|
|
// It's acceptable if the block limits data is outdated since
|
|
// there isn't a hard requirement on the unmap granularity.
|
|
//
|
|
ClasspBlockLimitsDataSnapshot(fdoExtension,
|
|
FALSE,
|
|
&blockLimitsData,
|
|
&generationCount);
|
|
|
|
//
|
|
// Check to see if the Optimal Unmap Granularity and Unmap Granularity
|
|
// Alignment have been specified. If not, default the granularity to
|
|
// one block and the alignment to zero.
|
|
//
|
|
if (blockLimitsData.OptimalUnmapGranularity != 0)
|
|
{
|
|
granularityInBlocks = blockLimitsData.OptimalUnmapGranularity;
|
|
}
|
|
else
|
|
{
|
|
granularityInBlocks = 1;
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClasspDeviceTrimProcess (%p): Optimal Unmap Granularity not provided, defaulted to 1.\n",
|
|
DeviceObject));
|
|
}
|
|
|
|
if (blockLimitsData.UGAVALID == TRUE)
|
|
{
|
|
granularityAlignmentInBytes = (ULONGLONG)blockLimitsData.UnmapGranularityAlignment * fdoExtension->DiskGeometry.BytesPerSector;
|
|
}
|
|
else
|
|
{
|
|
granularityAlignmentInBytes = 0;
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClasspDeviceTrimProcess (%p): Unmap Granularity Alignment not provided, defaulted to 0.\n",
|
|
DeviceObject));
|
|
}
|
|
|
|
if (TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE))
|
|
{
|
|
//
|
|
// The caller wants to UNMAP the entire disk so we need to build a single
|
|
// dataset range that represents the entire disk.
|
|
//
|
|
entireDataSetRange.StartingOffset = granularityAlignmentInBytes;
|
|
entireDataSetRange.LengthInBytes = (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart - (ULONGLONG)entireDataSetRange.StartingOffset;
|
|
|
|
dataSetRanges = &entireDataSetRange;
|
|
dataSetRangesCount = 1;
|
|
}
|
|
else
|
|
{
|
|
|
|
dataSetRanges = (PDEVICE_DATA_SET_RANGE)((PUCHAR)dsmAttributes + dsmAttributes->DataSetRangesOffset);
|
|
dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
|
|
|
|
//
|
|
// Validate the data ranges. Make sure the range is block-aligned,
|
|
// falls in a valid portion of the disk, and is non-zero.
|
|
//
|
|
for (i = 0; i < dataSetRangesCount; i++)
|
|
{
|
|
if ((dataSetRanges[i].StartingOffset % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
|
|
(dataSetRanges[i].LengthInBytes % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
|
|
(dataSetRanges[i].StartingOffset < (LONGLONG)granularityAlignmentInBytes) ||
|
|
(dataSetRanges[i].LengthInBytes == 0) ||
|
|
((ULONGLONG)dataSetRanges[i].StartingOffset + dataSetRanges[i].LengthInBytes > (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart))
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceTrimProcess (%p): Invalid dataset range. StartingOffset = %I64x, LengthInBytes = %I64x\n",
|
|
DeviceObject,
|
|
dataSetRanges[i].StartingOffset,
|
|
dataSetRanges[i].LengthInBytes));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (!TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED))
|
|
{
|
|
{
|
|
//
|
|
// For security reasons, file-level TRIM must be forwarded on only
|
|
// if reading the unmapped blocks' contents will return back zeros.
|
|
// This is because if LBPRZ bit is not set, it indicates that a read
|
|
// of unmapped blocks may return "any" data thus potentially leaking
|
|
// in data (into the read buffer) from other blocks.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning &&
|
|
!fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceTrimProcess (%p): Device does not support file level TRIM.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_TRIM_READ_ZERO_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// process DSM IOCTL
|
|
status = DeviceProcessDsmTrimRequest(fdoExtension,
|
|
dataSetRanges,
|
|
dataSetRangesCount,
|
|
granularityInBlocks,
|
|
srbFlags,
|
|
Irp,
|
|
ActivityId,
|
|
Srb);
|
|
} else {
|
|
// DSM IOCTL should be completed as not supported
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceTrimProcess (%p): Device does not support UNMAP.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
// set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
|
|
fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess = NotSupported;
|
|
|
|
break;
|
|
}
|
|
|
|
case Supported: {
|
|
NT_ASSERT(FALSE); // this case is handled at the begining of the function.
|
|
break;
|
|
}
|
|
|
|
} // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess)
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = 0;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
GetLBAStatus(
|
|
_In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
_In_ PSCSI_REQUEST_BLOCK Srb,
|
|
_In_ ULONGLONG StartingLBA,
|
|
_Inout_ PLBA_STATUS_LIST_HEADER LBAStatusHeader,
|
|
_In_ ULONG LBAStatusSize,
|
|
_In_ BOOLEAN ConsolidateableBlocksOnly
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send down a Get LBA Status command for the given range.
|
|
|
|
Arguments:
|
|
FdoExtension: The FDO extension of the device to which Get LBA Status will
|
|
be sent.
|
|
Srb: This should be allocated and initialized before it's passed in. It
|
|
will be used for the Get LBA Status command.
|
|
StartingLBA: The LBA that is at the beginning of the requested range.
|
|
LBAStatusHeader: Caller-allocated output buffer.
|
|
LBASTatusSize: Size of the caller-allocated output buffer.
|
|
|
|
Return Value:
|
|
|
|
Status of the operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PCDB cdb;
|
|
|
|
if (LBAStatusHeader == NULL || LBAStatusSize == 0)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Build and send down the Get LBA Status command.
|
|
//
|
|
SrbSetTimeOutValue(Srb, FdoExtension->TimeOutValue);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
SrbAssignSrbFlags(Srb, FdoExtension->SrbFlags);
|
|
SrbSetCdbLength(Srb, sizeof(cdb->GET_LBA_STATUS));
|
|
|
|
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->GET_LBA_STATUS.OperationCode = SCSIOP_GET_LBA_STATUS;
|
|
cdb->GET_LBA_STATUS.ServiceAction = SERVICE_ACTION_GET_LBA_STATUS;
|
|
REVERSE_BYTES_QUAD(&(cdb->GET_LBA_STATUS.StartingLBA), &StartingLBA);
|
|
REVERSE_BYTES(&(cdb->GET_LBA_STATUS.AllocationLength), &LBAStatusSize);
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"GetLBAStatus (%p): sending command with StartingLBA = 0x%I64x, AllocationLength = 0x%I64x, ConsolidateableBlocksOnly = %u\n",
|
|
FdoExtension->DeviceObject,
|
|
StartingLBA,
|
|
LBAStatusSize,
|
|
ConsolidateableBlocksOnly));
|
|
|
|
status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
|
|
Srb,
|
|
LBAStatusHeader,
|
|
LBAStatusSize,
|
|
FALSE);
|
|
|
|
//
|
|
// Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
|
|
// buffer was larger than necessary.
|
|
//
|
|
if (status == STATUS_DATA_OVERRUN &&
|
|
SrbGetDataTransferLength(Srb) < LBAStatusSize)
|
|
{
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
// log command.
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"GetLBAStatus (%p): command returned NT Status: %!STATUS!\n",
|
|
FdoExtension->DeviceObject,
|
|
status
|
|
));
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS ClasspDeviceGetLBAStatus(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function is to process IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES with DeviceDsmAction_Allocation.
|
|
|
|
1. This function will only handle the first dataset range.
|
|
2. This function will not handle dataset ranges whose LengthInBytes is greater than:
|
|
((MAXULONG - sizeof(LBA_STATUS_LIST_HEADER)) / sizeof(LBA_STATUS_DESCRIPTOR)) * BytesPerSlab
|
|
|
|
The input buffer should consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES followed
|
|
in memory by a single DEVICE_DATA_SET_RANGE that specifies the requested range
|
|
of slabs for which mapping status is desired.
|
|
|
|
The output buffer will consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT
|
|
followed in memory by a single DEVICE_DATA_SET_LB_PROVISIONING_STATE that
|
|
contains a bitmap that represents the mapped status of the slabs in the requested
|
|
range. Note that the number of slabs returned may be less than the number
|
|
requested.
|
|
|
|
Thus function will automatically re-align the given range offset if it was
|
|
not slab-aligned. The delta between the given range offset and the properly
|
|
aligned offset will be given in returned DEVICE_DATA_SET_LB_PROVISIONING_STATE.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO of the device to which Get LBA Status will be sent.
|
|
Irp: The IRP for the request. This function will read the input buffer and
|
|
write to the output buffer at the current IRP stack location.
|
|
Srb: This should be allocated and initialized before it's passed in. It
|
|
will be used for the Get LBA Status command.
|
|
|
|
Return Value:
|
|
|
|
STATUS_INVALID_PARAMETER: May be returned under the following conditions:
|
|
- If the requested range was too large. The caller should try again with a
|
|
smaller range. See above for how to calculate the maximum range.
|
|
- If the given starting offset was not within the valid range of the device.
|
|
STATUS_NOT_SUPPORTED: The storage did not report some information critical to
|
|
the execution of this function (e.g. Optimal Unmap Granularity).
|
|
STATUS_BUFFER_TOO_SMALL: The output buffer is not large enough to hold the max
|
|
data that could be returned from this function. If the output buffer is
|
|
at least the size of a ULONG, we will write the required output buffer size
|
|
to the first ULONG bytes of the output buffer.
|
|
STATUS_UNSUCCESSFUL: The Get LBA Status command succeeded but did not
|
|
return data as expected.
|
|
--*/
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = (PDEVICE_MANAGE_DATA_SET_ATTRIBUTES)Irp->AssociatedIrp.SystemBuffer;
|
|
PDEVICE_DATA_SET_RANGE dataSetRanges = NULL;
|
|
PDEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT dsmOutput = (PDEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT)Irp->AssociatedIrp.SystemBuffer;
|
|
ULONG dsmOutputLength;
|
|
NTSTATUS finalStatus;
|
|
NTSTATUS getLBAWorkerStatus;
|
|
ULONG retryCount;
|
|
ULONG retryCountMax;
|
|
CLASS_VPD_B0_DATA blockLimitsData;
|
|
ULONG generationCount1;
|
|
ULONG generationCount2;
|
|
BOOLEAN blockLimitsDataMayHaveChanged;
|
|
ULONG_PTR information = 0;
|
|
LONGLONG startingOffset;
|
|
ULONGLONG lengthInBytes;
|
|
BOOLEAN consolidateableBlocksOnly = FALSE;
|
|
ULONG outputVersion;
|
|
|
|
//
|
|
// Basic parameter validation.
|
|
// Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
|
|
//
|
|
if (dsmOutput == NULL ||
|
|
dsmAttributes == NULL)
|
|
{
|
|
finalStatus = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
|
|
if (TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE)) {
|
|
//
|
|
// The caller wants the mapping status of the entire disk.
|
|
//
|
|
ULONG unmapGranularityAlignment = 0;
|
|
if (fdoExtension->FunctionSupportInfo->BlockLimitsData.UGAVALID) {
|
|
unmapGranularityAlignment = fdoExtension->FunctionSupportInfo->BlockLimitsData.UnmapGranularityAlignment;
|
|
}
|
|
startingOffset = unmapGranularityAlignment;
|
|
lengthInBytes = (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart - (ULONGLONG)startingOffset;
|
|
} else {
|
|
if (dsmAttributes->DataSetRangesOffset == 0 ||
|
|
dsmAttributes->DataSetRangesLength == 0) {
|
|
finalStatus = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// We only service the first dataset range specified.
|
|
//
|
|
dataSetRanges = (PDEVICE_DATA_SET_RANGE)((PUCHAR)dsmAttributes + dsmAttributes->DataSetRangesOffset);
|
|
startingOffset = dataSetRanges[0].StartingOffset;
|
|
lengthInBytes = dataSetRanges[0].LengthInBytes;
|
|
}
|
|
|
|
|
|
//
|
|
// See if the sender is requesting a specific version of the output data
|
|
// structure. Othwerwise, default to V1.
|
|
//
|
|
outputVersion = DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1;
|
|
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
|
|
if ((dsmAttributes->ParameterBlockOffset >= sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) &&
|
|
(dsmAttributes->ParameterBlockLength >= sizeof(DEVICE_DATA_SET_LBP_STATE_PARAMETERS))) {
|
|
PDEVICE_DATA_SET_LBP_STATE_PARAMETERS parameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);
|
|
if ((parameters->Version == DEVICE_DATA_SET_LBP_STATE_PARAMETERS_VERSION_V1) &&
|
|
(parameters->Size >= sizeof(DEVICE_DATA_SET_LBP_STATE_PARAMETERS))) {
|
|
|
|
outputVersion = parameters->OutputVersion;
|
|
|
|
if ((outputVersion != DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1) &&
|
|
(outputVersion != DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2)) {
|
|
finalStatus = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Take a snapshot of the block limits data for the worker function to use.
|
|
// We need to fail the request if we fail to get updated block limits data
|
|
// since we need an accurate Optimal Unmap Granularity value to properly
|
|
// convert the returned mapping descriptors into a bitmap.
|
|
//
|
|
finalStatus = ClasspBlockLimitsDataSnapshot(fdoExtension,
|
|
TRUE,
|
|
&blockLimitsData,
|
|
&generationCount1);
|
|
|
|
if (!NT_SUCCESS(finalStatus)) {
|
|
information = 0;
|
|
goto Exit;
|
|
}
|
|
|
|
if (dsmAttributes->Flags & DEVICE_DSM_FLAG_ALLOCATION_CONSOLIDATEABLE_ONLY) {
|
|
consolidateableBlocksOnly = TRUE;
|
|
}
|
|
|
|
//
|
|
// The retry logic is to handle the case when block limits data changes during rare occasions
|
|
// (e.g. diff-VHD fork or merge).
|
|
//
|
|
retryCountMax = GET_LBA_STATUS_RETRY_COUNT_MAX;
|
|
for (retryCount = 0; retryCount < retryCountMax; retryCount++) {
|
|
|
|
dsmOutputLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
getLBAWorkerStatus = ClasspDeviceGetLBAStatusWorker(DeviceObject,
|
|
&blockLimitsData,
|
|
startingOffset,
|
|
lengthInBytes,
|
|
dsmOutput,
|
|
&dsmOutputLength,
|
|
Srb,
|
|
consolidateableBlocksOnly,
|
|
outputVersion,
|
|
&blockLimitsDataMayHaveChanged);
|
|
|
|
if (!NT_SUCCESS(getLBAWorkerStatus) && !blockLimitsDataMayHaveChanged) {
|
|
information = 0;
|
|
finalStatus = getLBAWorkerStatus;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Again, we need to fail the request if we fail to get updated block
|
|
// limits data since we need an accurate Optimal Unmap Granularity value.
|
|
//
|
|
finalStatus = ClasspBlockLimitsDataSnapshot(fdoExtension,
|
|
TRUE,
|
|
&blockLimitsData,
|
|
&generationCount2);
|
|
if (!NT_SUCCESS(finalStatus)) {
|
|
information = 0;
|
|
goto Exit;
|
|
}
|
|
|
|
if (generationCount1 == generationCount2) {
|
|
//
|
|
// Block limits data stays the same during the call to ClasspDeviceGetLBAStatusWorker()
|
|
// The result from ClasspDeviceGetLBAStatusWorker() is valid.
|
|
//
|
|
finalStatus = getLBAWorkerStatus;
|
|
if (NT_SUCCESS(finalStatus)) {
|
|
information = dsmOutputLength;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Try again with the latest block limits data
|
|
//
|
|
generationCount1 = generationCount2;
|
|
information = 0;
|
|
finalStatus = STATUS_DEVICE_DATA_ERROR;
|
|
}
|
|
|
|
Exit:
|
|
Irp->IoStatus.Information = information;
|
|
Irp->IoStatus.Status = finalStatus;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
return finalStatus;
|
|
}
|
|
|
|
NTSTATUS
|
|
ClasspDeviceGetLBAStatusWorker(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PCLASS_VPD_B0_DATA BlockLimitsData,
|
|
_In_ ULONGLONG StartingOffset,
|
|
_In_ ULONGLONG LengthInBytes,
|
|
_Out_ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT DsmOutput,
|
|
_Inout_ PULONG DsmOutputLength,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb,
|
|
_In_ BOOLEAN ConsolidateableBlocksOnly,
|
|
_In_ ULONG OutputVersion,
|
|
_Out_ PBOOLEAN BlockLimitsDataMayHaveChanged
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function is to process IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES with DeviceDsmAction_Allocation.
|
|
|
|
1. This function will only handle the first dataset range.
|
|
2. This function will not handle dataset ranges whose LengthInBytes is greater than:
|
|
((MAXULONG - sizeof(LBA_STATUS_LIST_HEADER)) / sizeof(LBA_STATUS_DESCRIPTOR)) * BytesPerSlab
|
|
|
|
The input buffer should consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES followed
|
|
in memory by a single DEVICE_DATA_SET_RANGE that specifies the requested range
|
|
of slabs for which mapping status is desired.
|
|
|
|
The output buffer will consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT
|
|
followed in memory by a single DEVICE_DATA_SET_LB_PROVISIONING_STATE that
|
|
contains a bitmap that represents the mapped status of the slabs in the requested
|
|
range. Note that the number of slabs returned may be less than the number
|
|
requested.
|
|
|
|
Thus function will automatically re-align the given range offset if it was
|
|
not slab-aligned. The delta between the given range offset and the properly
|
|
aligned offset will be given in returned DEVICE_DATA_SET_LB_PROVISIONING_STATE.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO of the device to which Get LBA Status will be sent.
|
|
BlockLimitsData: Block limits data of the device
|
|
StartingOffset: Starting byte offset of byte range to query LBA status (must be sector aligned)
|
|
LengthInBytes: Length of byte range to query LBA status (multiple of sector size)
|
|
DsmOutput: Output data buffer
|
|
DsmOutputLength: output data buffer size. It will be updated with actual bytes used.
|
|
Srb: This should be allocated and initialized before it's passed in. It
|
|
will be used for the Get LBA Status command.
|
|
ConsolidateableBlocksOnly: Only blocks that are eligible for consolidation
|
|
should be returned.
|
|
OutputVersion: The version of the DEVICE_DATA_SET_LB_PROVISIONING_STATE
|
|
structure to return. This should be one of:
|
|
- DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1
|
|
- DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2
|
|
BlockLimitsDataMayHaveChanged: if this function fails, this flag indicates
|
|
if the failure can be caused by changes in device's block limit data.
|
|
|
|
Return Value:
|
|
|
|
STATUS_INVALID_PARAMETER: May be returned under the following conditions:
|
|
- If the requested range was too large. The caller should try again with a
|
|
smaller range. See above for how to calculate the maximum range.
|
|
- If the given starting offset was not within the valid range of the device.
|
|
STATUS_NOT_SUPPORTED: The storage did not report some information critical to
|
|
the execution of this function (e.g. Optimal Unmap Granularity).
|
|
STATUS_BUFFER_TOO_SMALL: The output buffer is not large enough to hold the max
|
|
data that could be returned from this function. If the output buffer is
|
|
at least the size of a ULONG, we will write the required output buffer size
|
|
to the first ULONG bytes of the output buffer.
|
|
STATUS_DEVICE_DATA_ERROR: The Get LBA Status command succeeded but did not
|
|
return data as expected.
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
|
|
PDEVICE_DATA_SET_LB_PROVISIONING_STATE lbpState;
|
|
ULONG bitMapGranularityInBits = FIELD_SIZE(DEVICE_DATA_SET_LB_PROVISIONING_STATE,SlabAllocationBitMap[0]) * 8;
|
|
ULONG requiredOutputLength;
|
|
ULONG outputLength = *DsmOutputLength;
|
|
|
|
ULONG blocksPerSlab;
|
|
ULONGLONG bytesPerSlab;
|
|
ULONGLONG alignmentInBytes = 0;
|
|
ULONG alignmentInBlocks = 0;
|
|
ULONG maxBufferSize;
|
|
ULONG maxSlabs;
|
|
ULONGLONG requestedSlabs; // Total number of slabs requested by the caller.
|
|
ULONGLONG startingLBA;
|
|
ULONGLONG startingOffsetDelta;
|
|
ULONG totalProcessedSlabs = 0; // Total number of slabs we processed.
|
|
ULONGLONG slabsPerCommand; // Number of slabs we can ask for in one Get LBA Status command.
|
|
BOOLEAN doneProcessing = FALSE; // Indicates we should break out of the Get LBA Status loop.
|
|
|
|
ULONG lbaStatusSize;
|
|
PLBA_STATUS_LIST_HEADER lbaStatusListHeader = NULL;
|
|
|
|
//
|
|
// This function can fail if the block limits data on the device changes.
|
|
// This flag tells the caller if it should retry with a newer block limits data
|
|
//
|
|
*BlockLimitsDataMayHaveChanged = FALSE;
|
|
|
|
//
|
|
// Make sure we're running at PASSIVE_LEVEL
|
|
//
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
|
|
{
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Don't send down a Get LBA Status command if UNMAP isn't supported.
|
|
//
|
|
if (!fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU)
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Make sure we have a non-zero value for the number of bytes per block.
|
|
// Otherwise we will end up dividing by zero later on.
|
|
//
|
|
if (fdoExtension->DiskGeometry.BytesPerSector == 0)
|
|
{
|
|
status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
|
|
if(!NT_SUCCESS(status) || fdoExtension->DiskGeometry.BytesPerSector == 0)
|
|
{
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We only service the first dataset range specified.
|
|
//
|
|
if (BlockLimitsData->UGAVALID == TRUE) {
|
|
alignmentInBlocks = BlockLimitsData->UnmapGranularityAlignment;
|
|
alignmentInBytes = (ULONGLONG)alignmentInBlocks * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
|
|
}
|
|
|
|
//
|
|
// Make sure the specified range is valid. The Unmap Granularity Alignment
|
|
// defines a region at the beginning of the disk that cannot be
|
|
// mapped/unmapped so the specified range should not include any part of that
|
|
// region.
|
|
//
|
|
if (LengthInBytes == 0 ||
|
|
StartingOffset < alignmentInBytes ||
|
|
StartingOffset + LengthInBytes > (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart)
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Invalid range, length is %I64u bytes, starting offset is %I64u bytes, Unmap alignment is %I64u bytes, and disk size is %I64u bytes\n",
|
|
DeviceObject,
|
|
LengthInBytes,
|
|
StartingOffset,
|
|
alignmentInBytes,
|
|
(ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Calculate the number of bytes per slab so that we can convert (and
|
|
// possibly align) the given offset (given in bytes) to slabs.
|
|
//
|
|
blocksPerSlab = BlockLimitsData->OptimalUnmapGranularity;
|
|
bytesPerSlab = (ULONGLONG)blocksPerSlab * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
|
|
|
|
//
|
|
// If the starting offset is not slab-aligned, we need to adjust it to
|
|
// be aligned with the next highest slab. We also need to save the delta
|
|
// to return to the user later.
|
|
//
|
|
if (((StartingOffset - alignmentInBytes) % bytesPerSlab) != 0)
|
|
{
|
|
startingLBA = (((StartingOffset - alignmentInBytes) / bytesPerSlab) + 1) * (ULONGLONG)blocksPerSlab + alignmentInBlocks;
|
|
startingOffsetDelta = (startingLBA * fdoExtension->DiskGeometry.BytesPerSector) - StartingOffset;
|
|
}
|
|
else
|
|
{
|
|
startingLBA = ((StartingOffset - alignmentInBytes) / bytesPerSlab) * (ULONGLONG)blocksPerSlab + alignmentInBlocks;
|
|
startingOffsetDelta = 0;
|
|
}
|
|
|
|
//
|
|
// Caclulate the number of slabs the caller requested.
|
|
//
|
|
if ((LengthInBytes % bytesPerSlab) == 0) {
|
|
requestedSlabs = (LengthInBytes / bytesPerSlab);
|
|
} else {
|
|
//
|
|
// Round up the number of requested slabs if the length indicates a
|
|
// partial slab. This should cover the case where the user specifies
|
|
// a dataset range for the whole disk, but the size of the disk is not
|
|
// a slab-multiple. Rounding up allows us to return the status of the
|
|
// partial slab
|
|
//
|
|
requestedSlabs = (LengthInBytes / bytesPerSlab) + 1;
|
|
}
|
|
|
|
//
|
|
// If the caller asked for no slabs then return STATUS_INVALID_PARAMETER.
|
|
//
|
|
if (requestedSlabs == 0)
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Invalid number (%I64u) of slabs requested\n",
|
|
DeviceObject,
|
|
requestedSlabs));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Cap requested slabs at MAXULONG since SlabAllocationBitMapBitCount
|
|
// is a 4-byte field. We may return less data than requested, but the
|
|
// caller can simply re-query for the omitted portion(s).
|
|
//
|
|
requestedSlabs = min(requestedSlabs, MAXULONG);
|
|
|
|
//
|
|
// Calculate the required size of the output buffer based upon the desired
|
|
// version of the output structure.
|
|
// In the worst case, Get LBA Status returns a descriptor for each slab
|
|
// requested, thus the required output buffer length is equal to:
|
|
// 1. The size of DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT; plus
|
|
// 2. The size of DEVICE_DATA_SET_LB_PROVISIONING_STATE(_V2); plus
|
|
// 3. The size of a ULONG array large enough to hold a bit for each slab requested.
|
|
// (The first element is already allocated in DEVICE_DATA_SET_LB_PROVISIONING_STATE(_V2).)
|
|
//
|
|
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
|
|
if (OutputVersion == DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2) {
|
|
|
|
requiredOutputLength = (ULONG)(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT)
|
|
+ sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)
|
|
+ (((requestedSlabs - 1) / bitMapGranularityInBits))
|
|
* FIELD_SIZE(DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2, SlabAllocationBitMap[0]));
|
|
|
|
} else
|
|
#else
|
|
UNREFERENCED_PARAMETER(OutputVersion);
|
|
#endif
|
|
{
|
|
|
|
requiredOutputLength = (ULONG)(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT)
|
|
+ sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE)
|
|
+ (((requestedSlabs - 1) / bitMapGranularityInBits))
|
|
* FIELD_SIZE(DEVICE_DATA_SET_LB_PROVISIONING_STATE, SlabAllocationBitMap[0]));
|
|
}
|
|
|
|
//
|
|
// The output buffer is not big enough to hold the requested data.
|
|
// Inform the caller of the correct buffer size.
|
|
//
|
|
if (outputLength < requiredOutputLength)
|
|
{
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Given output buffer is %u bytes, needs to be %u bytes\n",
|
|
DeviceObject,
|
|
outputLength,
|
|
requiredOutputLength));
|
|
|
|
//
|
|
// If the output buffer is big enough, write the required buffer
|
|
// length to the first ULONG bytes of the output buffer.
|
|
//
|
|
if (outputLength >= sizeof(ULONG))
|
|
{
|
|
*((PULONG)DsmOutput) = requiredOutputLength;
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Calculate the maximum number of slabs that could be returned by a single
|
|
// Get LBA Status command. The max buffer size could either be capped by
|
|
// the Parameter Data Length field or the Max Transfer Length of the
|
|
// adapter.
|
|
// The number of slabs we actually ask for in a single command is the
|
|
// smaller of the number of slabs requested by the user or the max number
|
|
// of slabs we can theoretically ask for in a single command.
|
|
//
|
|
maxBufferSize = MIN(MAXULONG, fdoExtension->PrivateFdoData->HwMaxXferLen);
|
|
maxSlabs = (maxBufferSize - sizeof(LBA_STATUS_LIST_HEADER)) / sizeof(LBA_STATUS_DESCRIPTOR);
|
|
slabsPerCommand = min(requestedSlabs, maxSlabs);
|
|
|
|
//
|
|
// Allocate the buffer that will contain the returned LBA Status Descriptors.
|
|
// Assume that in the worst case every other slab has a different mapping
|
|
// status. That means that there may be a descriptor for every slab requested.
|
|
//
|
|
lbaStatusSize = (ULONG)(sizeof(LBA_STATUS_LIST_HEADER) + (slabsPerCommand * sizeof(LBA_STATUS_DESCRIPTOR)));
|
|
#if defined(_ARM_) || defined(_ARM64_)
|
|
//
|
|
// ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
|
|
// based platforms. We are taking the conservative approach here.
|
|
//
|
|
lbaStatusSize = ALIGN_UP_BY(lbaStatusSize,KeGetRecommendedSharedDataAlignment());
|
|
lbaStatusListHeader = (PLBA_STATUS_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, lbaStatusSize, CLASS_TAG_LB_PROVISIONING);
|
|
#else
|
|
lbaStatusListHeader = (PLBA_STATUS_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNx, lbaStatusSize, CLASS_TAG_LB_PROVISIONING);
|
|
#endif
|
|
|
|
if (lbaStatusListHeader == NULL)
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Failed to allocate %u bytes for descriptors\n",
|
|
DeviceObject,
|
|
lbaStatusSize));
|
|
|
|
NT_ASSERT(lbaStatusListHeader != NULL);
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Set default values for the output buffer.
|
|
// If we process at least one slab from the device we will update the
|
|
// offset and lengths accordingly.
|
|
//
|
|
DsmOutput->Action = DeviceDsmAction_Allocation;
|
|
DsmOutput->Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT);
|
|
DsmOutput->OutputBlockOffset = 0;
|
|
DsmOutput->OutputBlockLength = 0;
|
|
*DsmOutputLength = DsmOutput->Size;
|
|
|
|
//
|
|
// The returned DEVICE_DATA_SET_LB_PROVISIONING_STATE is at the end of the
|
|
// DSM output structure. Zero it out before we start to fill it in.
|
|
//
|
|
lbpState = Add2Ptr(DsmOutput, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT));
|
|
RtlZeroMemory(lbpState, requiredOutputLength - sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT));
|
|
|
|
do {
|
|
//
|
|
// Send down GetLBAStatus for the current range.
|
|
//
|
|
status = GetLBAStatus(fdoExtension,
|
|
Srb,
|
|
startingLBA,
|
|
lbaStatusListHeader,
|
|
lbaStatusSize,
|
|
ConsolidateableBlocksOnly);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
ULONG descrIndex = 0;
|
|
ULONG descrSize = 0;
|
|
ULONG descrSizeOverhead;
|
|
ULONG descrCount = 0;
|
|
ULONGLONG expectedStartingLBA;
|
|
BOOLEAN processCurrentDescriptor = TRUE;
|
|
ULONG commandProcessedSlabs = 0; // Number of slabs processed for this command.
|
|
|
|
descrSizeOverhead = FIELD_OFFSET(LBA_STATUS_LIST_HEADER, Descriptors[0]) -
|
|
RTL_SIZEOF_THROUGH_FIELD(LBA_STATUS_LIST_HEADER, ParameterLength);
|
|
REVERSE_BYTES(&descrSize, &(lbaStatusListHeader->ParameterLength));
|
|
|
|
//
|
|
// If the returned Parameter Data Length field describes more
|
|
// descriptors than we allocated space for then make sure we don't
|
|
// try to process more descriptors than are actually present.
|
|
//
|
|
if (descrSize > (lbaStatusSize - RTL_SIZEOF_THROUGH_FIELD(LBA_STATUS_LIST_HEADER, ParameterLength))) {
|
|
descrSize = (lbaStatusSize - RTL_SIZEOF_THROUGH_FIELD(LBA_STATUS_LIST_HEADER, ParameterLength));
|
|
}
|
|
|
|
if (descrSize >= descrSizeOverhead) {
|
|
descrSize -= descrSizeOverhead;
|
|
descrCount = descrSize / sizeof(LBA_STATUS_DESCRIPTOR);
|
|
|
|
//
|
|
// Make sure at least one descriptor was returned.
|
|
//
|
|
if (descrCount > 0) {
|
|
//
|
|
// We expect the first starting LBA returned by the device to be the
|
|
// same starting LBA we specified in the command.
|
|
//
|
|
expectedStartingLBA = startingLBA;
|
|
|
|
//
|
|
// Translate the returned LBA status descriptors into a bitmap where each bit represents
|
|
// a slab. The slab size is represented by the Optimal Unmap Granularity.
|
|
// 1 = The slab is mapped.
|
|
// 0 = The slab is unmapped (deallocated or anchored).
|
|
//
|
|
for (descrIndex = 0; descrIndex < descrCount && totalProcessedSlabs < requestedSlabs && !doneProcessing; descrIndex++)
|
|
{
|
|
PLBA_STATUS_DESCRIPTOR lbaStatusDescr = &(lbaStatusListHeader->Descriptors[descrIndex]);
|
|
ULONGLONG returnedStartingLBA;
|
|
ULONG mapped = (lbaStatusDescr->ProvisioningStatus != LBA_STATUS_MAPPED) ? 0x0 : 0x1;
|
|
ULONG lbaCount = 0;
|
|
|
|
REVERSE_BYTES_QUAD(&returnedStartingLBA, &(lbaStatusDescr->StartingLBA));
|
|
REVERSE_BYTES(&lbaCount, &(lbaStatusDescr->LogicalBlockCount));
|
|
|
|
if (returnedStartingLBA != expectedStartingLBA)
|
|
{
|
|
//
|
|
// We expect the descriptors will express a contiguous range of LBAs.
|
|
// If the starting LBA is not contiguous with the LBA range from the
|
|
// previous descriptor then we should not process any more descriptors,
|
|
// including the current one.
|
|
//
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Device returned starting LBA = %I64x when %I64x was expected.\n",
|
|
DeviceObject,
|
|
returnedStartingLBA,
|
|
startingLBA));
|
|
|
|
doneProcessing = TRUE;
|
|
processCurrentDescriptor = FALSE;
|
|
*BlockLimitsDataMayHaveChanged = TRUE;
|
|
}
|
|
else if (lbaCount > 0 && lbaCount % blocksPerSlab != 0)
|
|
{
|
|
//
|
|
// If the device returned an LBA count with a partial slab, round
|
|
// the LBA count up to the nearest slab and set a flag to stop
|
|
// processing further descriptors. This is mainly to handle the
|
|
// case where disk size may not be slab-aligned and thus the last
|
|
// "slab" is actually a partial slab.
|
|
//
|
|
TracePrint((TRACE_LEVEL_WARNING,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Device returned an LBA count (%u) that is not a multiple of the slab size (%u)\n",
|
|
DeviceObject,
|
|
lbaCount,
|
|
blocksPerSlab));
|
|
|
|
lbaCount = ((lbaCount / blocksPerSlab) + 1) * blocksPerSlab;
|
|
|
|
doneProcessing = TRUE;
|
|
processCurrentDescriptor = TRUE;
|
|
}
|
|
else if (lbaCount == 0)
|
|
{
|
|
//
|
|
// If the LBA count is 0, just skip this descriptor.
|
|
//
|
|
TracePrint((TRACE_LEVEL_WARNING,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Device returned a zero LBA count\n",
|
|
DeviceObject));
|
|
|
|
processCurrentDescriptor = FALSE;
|
|
}
|
|
|
|
//
|
|
// Generate bits for the slabs described in the current descriptor.
|
|
// It's possible the device may have returned more slabs than requested
|
|
// so we make sure to stop once we've processed all we need.
|
|
//
|
|
if (processCurrentDescriptor)
|
|
{
|
|
ULONG descrSlabs = lbaCount / blocksPerSlab; // Number of slabs in this descriptor.
|
|
|
|
for(; 0 < descrSlabs && totalProcessedSlabs < requestedSlabs; descrSlabs--, commandProcessedSlabs++, totalProcessedSlabs++)
|
|
{
|
|
ULONG bitMapIndex = totalProcessedSlabs / bitMapGranularityInBits;
|
|
ULONG bitPos = totalProcessedSlabs % bitMapGranularityInBits;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
|
|
if (OutputVersion == DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2) {
|
|
((PDEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)lbpState)->SlabAllocationBitMap[bitMapIndex] |= (mapped << bitPos);
|
|
} else
|
|
#endif
|
|
{
|
|
lbpState->SlabAllocationBitMap[bitMapIndex] |= (mapped << bitPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Calculate the next expected starting LBA.
|
|
//
|
|
expectedStartingLBA = returnedStartingLBA + lbaCount;
|
|
}
|
|
|
|
if (commandProcessedSlabs > 0) {
|
|
|
|
//
|
|
// Calculate the starting LBA we'll use for the next command.
|
|
//
|
|
startingLBA += ((ULONGLONG)commandProcessedSlabs * (ULONGLONG)blocksPerSlab);
|
|
|
|
} else {
|
|
//
|
|
// This should never happen, but we should handle it gracefully anyway.
|
|
//
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): The slab allocation bitmap has zero length.\n",
|
|
DeviceObject));
|
|
|
|
NT_ASSERT(commandProcessedSlabs != 0);
|
|
doneProcessing = TRUE;
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
} else {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Device returned no LBA Status Descriptors.\n",
|
|
DeviceObject));
|
|
|
|
doneProcessing = TRUE;
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
} else {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): not enough bytes returned\n",
|
|
DeviceObject));
|
|
|
|
doneProcessing = TRUE;
|
|
status = STATUS_DEVICE_DATA_ERROR;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop until we encounter some error or we've processed all the requested slabs.
|
|
//
|
|
} while (NT_SUCCESS(status) &&
|
|
!doneProcessing &&
|
|
(totalProcessedSlabs < requestedSlabs));
|
|
|
|
//
|
|
// At least one slab was returned by the device and processed, which we
|
|
// consider success. It's up to the caller to detect truncation.
|
|
// Update the output buffer sizes, offsets, etc. accordingly.
|
|
//
|
|
if (totalProcessedSlabs > 0) {
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
|
|
if (OutputVersion == DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2) {
|
|
PDEVICE_DATA_SET_LB_PROVISIONING_STATE_V2 lbpStateV2 = (PDEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)lbpState;
|
|
|
|
lbpStateV2->SlabSizeInBytes = bytesPerSlab;
|
|
lbpStateV2->SlabOffsetDeltaInBytes = startingOffsetDelta;
|
|
lbpStateV2->SlabAllocationBitMapBitCount = totalProcessedSlabs;
|
|
lbpStateV2->SlabAllocationBitMapLength = ((totalProcessedSlabs - 1) / (ULONGLONG)bitMapGranularityInBits) + 1;
|
|
lbpStateV2->Version = DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2;
|
|
|
|
//
|
|
// Note that there is already one element of the bitmap array allocated
|
|
// in the DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2 structure itself, which
|
|
// is why we subtract 1 from SlabAllocationBitMapLength.
|
|
//
|
|
lbpStateV2->Size = sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)
|
|
+ ((lbpStateV2->SlabAllocationBitMapLength - 1) * sizeof(lbpStateV2->SlabAllocationBitMap[0]));
|
|
|
|
} else
|
|
#endif
|
|
{
|
|
|
|
lbpState->SlabSizeInBytes = bytesPerSlab;
|
|
lbpState->SlabOffsetDeltaInBytes = (ULONG)startingOffsetDelta;
|
|
lbpState->SlabAllocationBitMapBitCount = totalProcessedSlabs;
|
|
lbpState->SlabAllocationBitMapLength = ((totalProcessedSlabs - 1) / bitMapGranularityInBits) + 1;
|
|
lbpState->Version = DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1;
|
|
|
|
//
|
|
// Note that there is already one element of the bitmap array allocated
|
|
// in the DEVICE_DATA_SET_LB_PROVISIONING_STATE structure itself, which
|
|
// is why we subtract 1 from SlabAllocationBitMapLength.
|
|
//
|
|
lbpState->Size = sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE)
|
|
+ ((lbpState->SlabAllocationBitMapLength - 1) * sizeof(lbpState->SlabAllocationBitMap[0]));
|
|
}
|
|
|
|
DsmOutput->OutputBlockLength = lbpState->Size; // Size is at the same offset in all versions of the structure.
|
|
DsmOutput->OutputBlockOffset = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT);
|
|
*DsmOutputLength = DsmOutput->Size + DsmOutput->OutputBlockLength;
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceGetLBAStatusWorker (%p): Processed a total of %u slabs\n",
|
|
DeviceObject,
|
|
totalProcessedSlabs));
|
|
Exit:
|
|
|
|
FREE_POOL(lbaStatusListHeader);
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClassGetLBProvisioningLogPage(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PSCSI_REQUEST_BLOCK Srb,
|
|
_In_ ULONG LogPageSize,
|
|
_Inout_ PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING LogPage
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function sends a LOG SENSE command to the given device and returns the
|
|
Logical Block Provisioning Log Page, if available.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO of the device to which the Log Sense command will be sent.
|
|
Srb: This should be allocated before it is passed in, but it does not have
|
|
to be initialized. This function will initialize it.
|
|
LogPageSize: The size of the LogPage buffer in bytes.
|
|
LogPage: A pointer to an already allocated output buffer that may contain
|
|
the LBP log page when this function returns.
|
|
|
|
Return Value:
|
|
|
|
STATUS_INVALID_PARAMETER: May be returned if the LogPage buffer is NULL or
|
|
not large enough.
|
|
STATUS_SUCCESS: The log page was obtained and placed in the LogPage buffer.
|
|
|
|
This function may return other NTSTATUS codes from internal function calls.
|
|
--*/
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PCDB cdb = NULL;
|
|
|
|
//
|
|
// Make sure the caller passed in an adequate output buffer. The Allocation
|
|
// Length field in the Log Sense command is only 2 bytes so we need to also
|
|
// make sure that the given log page size isn't larger than MAXUSHORT.
|
|
//
|
|
if (LogPage == NULL ||
|
|
LogPageSize < sizeof(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING) ||
|
|
LogPageSize > MAXUSHORT)
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassGetLBProvisioningLogPage: DO (%p), Invalid parameter, LogPage = %p, LogPageSize = %u.\n",
|
|
DeviceObject,
|
|
LogPage,
|
|
LogPageSize));
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Initialize the SRB.
|
|
//
|
|
if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
|
|
status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)Srb,
|
|
STORAGE_ADDRESS_TYPE_BTL8,
|
|
CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
|
|
1,
|
|
SrbExDataTypeScsiCdb16);
|
|
if (NT_SUCCESS(status)) {
|
|
((PSTORAGE_REQUEST_BLOCK)Srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
|
|
} else {
|
|
//
|
|
// Should not occur.
|
|
//
|
|
NT_ASSERT(FALSE);
|
|
}
|
|
} else {
|
|
RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
}
|
|
|
|
//
|
|
// Build and send down the Log Sense command.
|
|
//
|
|
SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
|
|
SrbSetRequestTag(Srb, SP_UNTAGGED);
|
|
SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
|
|
SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);
|
|
SrbSetCdbLength(Srb, sizeof(cdb->LOGSENSE));
|
|
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->LOGSENSE.OperationCode = SCSIOP_LOG_SENSE;
|
|
cdb->LOGSENSE.PageCode = LOG_PAGE_CODE_LOGICAL_BLOCK_PROVISIONING;
|
|
cdb->LOGSENSE.PCBit = 0;
|
|
cdb->LOGSENSE.ParameterPointer[0] = 0;
|
|
cdb->LOGSENSE.ParameterPointer[1] = 0;
|
|
REVERSE_BYTES_SHORT(&(cdb->LOGSENSE.AllocationLength), &LogPageSize);
|
|
|
|
status = ClassSendSrbSynchronous(fdoExtension->DeviceObject,
|
|
Srb,
|
|
LogPage,
|
|
LogPageSize,
|
|
FALSE);
|
|
|
|
//
|
|
// Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
|
|
// buffer was larger than necessary.
|
|
//
|
|
if (status == STATUS_DATA_OVERRUN &&
|
|
SrbGetDataTransferLength(Srb) < LogPageSize)
|
|
{
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Log the command.
|
|
//
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClassGetLBProvisioningLogPage: DO (%p), LogSense command issued for LBP log page. NT Status: %!STATUS!.\n",
|
|
DeviceObject,
|
|
status
|
|
));
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS ClassInterpretLBProvisioningLogPage(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ ULONG LogPageSize,
|
|
_In_ PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING LogPage,
|
|
_In_ ULONG ResourcesSize,
|
|
_Out_ PSTORAGE_LB_PROVISIONING_MAP_RESOURCES Resources
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function takes a Logical Block Provisioning log page (returned by
|
|
ClassGetLBProvisioningLogPage(), for example), interprets its contents,
|
|
and returns the interpreted data in a STORAGE_LB_PROVISIONING_MAP_RESOURCES
|
|
structure.
|
|
|
|
None, some, or all of the data in the output buffer may be valid. The
|
|
caller must look at the individual "Valid" fields to see which fields have
|
|
valid data.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO of the device from which the log page was obtained.
|
|
LogPageSize: The size of the LogPage buffer in bytes.
|
|
LogPage: A pointer to a valid LBP log page structure.
|
|
ResourcesSize: The size of the Resources buffer in bytes.
|
|
Resources: A pointer to an already allocated output buffer that may contain
|
|
the interpreted log page data when this function returns.
|
|
|
|
Return Value:
|
|
|
|
STATUS_NOT_SUPPORTED: May be returned if the threshold exponent from the
|
|
0xB2 page is invalid.
|
|
STATUS_INVALID_PARAMETER: May be returned if either the LogPage or Resources
|
|
buffers are NULL or too small.
|
|
STATUS_SUCCESS: The log page data was interpreted and the Resources output
|
|
buffer has data in it.
|
|
|
|
This function may return other NTSTATUS codes from internal function calls.
|
|
--*/
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
USHORT pageLength;
|
|
PLOG_PARAMETER_HEADER parameter;
|
|
PVOID endOfPage;
|
|
USHORT parameterCode;
|
|
ULONG resourceCount;
|
|
UCHAR thresholdExponent = fdoExtension->FunctionSupportInfo->LBProvisioningData.ThresholdExponent;
|
|
ULONGLONG thresholdSetSize;
|
|
|
|
//
|
|
// SBC-3 states that the threshold exponent (from the 0xB2 VPD page), must
|
|
// be non-zero and less than or equal to 32.
|
|
//
|
|
if (thresholdExponent < 0 || thresholdExponent > 32)
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassInterpretLBProvisioningLogPage: DO (%p), Threshold Exponent (%u) is invalid.\n",
|
|
DeviceObject,
|
|
thresholdExponent));
|
|
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (Resources == NULL ||
|
|
ResourcesSize < sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES) ||
|
|
LogPage == NULL ||
|
|
LogPageSize < sizeof(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING))
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassInterpretLBProvisioningLogPage: DO (%p), Invalid parameter, Resources = %p, ResourcesSize = %u, LogPage = %p, LogPageSize = %u.\n",
|
|
DeviceObject,
|
|
Resources,
|
|
ResourcesSize,
|
|
LogPage,
|
|
LogPageSize));
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Calculate the threshold set size (in LBAs).
|
|
//
|
|
thresholdSetSize = 1ULL << thresholdExponent;
|
|
|
|
REVERSE_BYTES_SHORT(&pageLength, &(LogPage->PageLength));
|
|
|
|
//
|
|
// Initialize the output buffer.
|
|
//
|
|
RtlZeroMemory(Resources, sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES));
|
|
Resources->Size = sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES);
|
|
Resources->Version = sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES);
|
|
|
|
//
|
|
// Make sure we don't walk off the end of the log page buffer
|
|
// if pageLength is somehow longer than the buffer itself.
|
|
//
|
|
pageLength = (USHORT)min(pageLength, (LogPageSize - FIELD_OFFSET(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING, Parameters)));
|
|
|
|
parameter = (PLOG_PARAMETER_HEADER)((PUCHAR)LogPage + FIELD_OFFSET(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING, Parameters));
|
|
endOfPage = (PVOID)((PUCHAR)parameter + pageLength);
|
|
|
|
//
|
|
// Walk the parameters.
|
|
//
|
|
while ((PVOID)parameter < endOfPage)
|
|
{
|
|
if (parameter->ParameterLength > 0)
|
|
{
|
|
REVERSE_BYTES_SHORT(¶meterCode, &(parameter->ParameterCode));
|
|
switch(parameterCode)
|
|
{
|
|
case LOG_PAGE_LBP_PARAMETER_CODE_AVAILABLE:
|
|
{
|
|
REVERSE_BYTES(&resourceCount, &(((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->ResourceCount));
|
|
Resources->AvailableMappingResources = (ULONGLONG)resourceCount * thresholdSetSize * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
|
|
Resources->AvailableMappingResourcesValid = TRUE;
|
|
|
|
//
|
|
// Devices that implement SBC-3 revisions older than r27 will not specify
|
|
// an LBP log page parameter that has fields beyond ResourceCount.
|
|
//
|
|
if (parameter->ParameterLength > FIELD_OFFSET(LOG_PARAMETER_THRESHOLD_RESOURCE_COUNT, ResourceCount[3])) {
|
|
Resources->AvailableMappingResourcesScope = ((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->Scope;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case LOG_PAGE_LBP_PARAMETER_CODE_USED:
|
|
{
|
|
REVERSE_BYTES(&resourceCount, &(((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->ResourceCount));
|
|
Resources->UsedMappingResources = (ULONGLONG)resourceCount * thresholdSetSize * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
|
|
Resources->UsedMappingResourcesValid = TRUE;
|
|
|
|
//
|
|
// Devices that implement SBC-3 revisions older than r27 will not specify
|
|
// an LBP log page parameter that has fields beyond ResourceCount.
|
|
//
|
|
if (parameter->ParameterLength > FIELD_OFFSET(LOG_PARAMETER_THRESHOLD_RESOURCE_COUNT, ResourceCount[3])) {
|
|
Resources->UsedMappingResourcesScope = ((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->Scope;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move to the next parameter.
|
|
//
|
|
parameter = (PLOG_PARAMETER_HEADER)((PUCHAR)parameter + sizeof(LOG_PARAMETER_HEADER) + parameter->ParameterLength);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS ClassGetLBProvisioningResources(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb,
|
|
_In_ ULONG ResourcesSize,
|
|
_Inout_ PSTORAGE_LB_PROVISIONING_MAP_RESOURCES Resources
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function obtains the Logical Block Provisioning log page, interprets
|
|
its contents, and returns the interpreted data in a
|
|
STORAGE_LB_PROVISIONING_MAP_RESOURCES structure.
|
|
|
|
None, some, or all of the data in the output buffer may be valid. The
|
|
caller must look at the individual "Valid" fields to see which fields have
|
|
valid data.
|
|
|
|
Arguments:
|
|
DeviceObject: The target FDO.
|
|
Srb: This should be allocated before it is passed in, but it does not have
|
|
to be initialized.
|
|
ResourcesSize: The size of the Resources buffer in bytes.
|
|
Resources: A pointer to an already allocated output buffer that may contain
|
|
the interpreted log page data when this function returns.
|
|
|
|
Return Value:
|
|
|
|
STATUS_NOT_SUPPORTED: May be returned if the device does not have LBP enabled.
|
|
STATUS_INVALID_PARAMETER: May be returned if either the Resources buffer is
|
|
NULL or too small.
|
|
STATUS_INSUFFICIENT_RESOURCES: May be returned if a log page buffer could not
|
|
be allocated.
|
|
STATUS_SUCCESS: The log page data was obtained and the Resources output
|
|
buffer has data in it.
|
|
|
|
This function may return other NTSTATUS codes from internal function calls.
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
ULONG logPageSize;
|
|
PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING logPage = NULL;
|
|
|
|
//
|
|
// This functionality is only supported for devices that support logical
|
|
// block provisioning.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == FALSE)
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassGetLBProvisioningResources: DO (%p), Device does not support logical block provisioning.\n",
|
|
DeviceObject));
|
|
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
//
|
|
// Validate the output buffer.
|
|
//
|
|
if (Resources == NULL ||
|
|
ResourcesSize < sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES))
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassGetLBProvisioningResources: DO (%p), Invalid parameter, Resources = %p, ResourcesSize = %u.\n",
|
|
DeviceObject,
|
|
Resources,
|
|
ResourcesSize));
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Allocate a buffer for the log page. Currently the log page contains:
|
|
// 1. Log page header
|
|
// 2. Log page parameter for used resources
|
|
// 3. Log page parameter for available resources
|
|
//
|
|
logPageSize = sizeof(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING) + (2 * sizeof(LOG_PARAMETER_THRESHOLD_RESOURCE_COUNT));
|
|
|
|
#if defined(_ARM_) || defined(_ARM64_)
|
|
//
|
|
// ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
|
|
// based platforms. We are taking the conservative approach here.
|
|
//
|
|
logPageSize = ALIGN_UP_BY(logPageSize, KeGetRecommendedSharedDataAlignment());
|
|
logPage = (PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, logPageSize, CLASS_TAG_LB_PROVISIONING);
|
|
#else
|
|
logPage = (PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING)ExAllocatePoolWithTag(NonPagedPoolNx, logPageSize, CLASS_TAG_LB_PROVISIONING);
|
|
#endif
|
|
if (logPage != NULL)
|
|
{
|
|
//
|
|
// Get the LBP log page from the device.
|
|
//
|
|
status = ClassGetLBProvisioningLogPage(DeviceObject,
|
|
Srb,
|
|
logPageSize,
|
|
logPage);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
//
|
|
// Interpret the log page and fill in the output buffer.
|
|
//
|
|
status = ClassInterpretLBProvisioningLogPage(DeviceObject,
|
|
logPageSize,
|
|
logPage,
|
|
ResourcesSize,
|
|
Resources);
|
|
}
|
|
|
|
ExFreePool(logPage);
|
|
}
|
|
else
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassGetLBProvisioningResources: DO (%p), Failed to allocate memory for LBP log page.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
ClassDeviceGetLBProvisioningResources(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function returns the LBP resource counts in a
|
|
STORAGE_LB_PROVISIONING_MAP_RESOURCES structure in the IRP.
|
|
|
|
None, some, or all of the data in the output buffer may be valid. The
|
|
caller must look at the individual "Valid" fields to see which fields have
|
|
valid data.
|
|
|
|
Arguments:
|
|
DeviceObject: The target FDO.
|
|
Irp: The IRP which will contain the output buffer upon completion.
|
|
Srb: This should be allocated before it is passed in, but it does not have
|
|
to be initialized.
|
|
|
|
Return Value:
|
|
|
|
Some NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSTORAGE_LB_PROVISIONING_MAP_RESOURCES mapResources = (PSTORAGE_LB_PROVISIONING_MAP_RESOURCES)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
status = ClassGetLBProvisioningResources(DeviceObject,
|
|
Srb,
|
|
irpStack->Parameters.DeviceIoControl.OutputBufferLength,
|
|
mapResources);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
Irp->IoStatus.Information = mapResources->Size;
|
|
} else {
|
|
Irp->IoStatus.Information = 0;
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
_Function_class_(IO_WORKITEM_ROUTINE)
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
VOID
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClassLogThresholdEvent(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function logs a logical block provisioning soft threshold event to the
|
|
system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the soft
|
|
threshold.
|
|
Context: A pointer to the IO_WORKITEM in which this function is running.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PSCSI_REQUEST_BLOCK srb = NULL;
|
|
STORAGE_LB_PROVISIONING_MAP_RESOURCES resources = {0};
|
|
ULONG resourcesSize = sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES);
|
|
PIO_ERROR_LOG_PACKET errorLogEntry = NULL;
|
|
ULONG logEntrySize = sizeof(IO_ERROR_LOG_PACKET);
|
|
PWCHAR stringIndex = NULL;
|
|
LONG stringSize = 0;
|
|
ULONG srbSize;
|
|
|
|
//
|
|
// Allocate an SRB for getting the LBP log page.
|
|
//
|
|
if ((fdoExtension->AdapterDescriptor != NULL) &&
|
|
(fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
|
|
srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
|
|
} else {
|
|
srbSize = sizeof(SCSI_REQUEST_BLOCK);
|
|
}
|
|
|
|
srb = ExAllocatePoolWithTag(NonPagedPoolNx,
|
|
srbSize,
|
|
'ACcS');
|
|
if (srb != NULL) {
|
|
|
|
//
|
|
// Try to get the LBP resources from the device so we can report them in
|
|
// the system event log.
|
|
//
|
|
ClassGetLBProvisioningResources(DeviceObject,
|
|
srb,
|
|
resourcesSize,
|
|
&resources);
|
|
|
|
//
|
|
// We need to allocate enough space for 3 insertion strings:
|
|
// The first is a ULONG representing the disk number in decimal, which means
|
|
// a max of 10 digits, plus one for the NULL character.
|
|
// The second and third are ULONGLONGs representing the used and available
|
|
// bytes, which means a max of 20 digits, plus one for the NULL character.
|
|
// Make sure we do not exceed the max error log size or the max size of a
|
|
// UCHAR since the size gets truncated to a UCHAR when we pass it to
|
|
// IoAllocateErrorLogEntry().
|
|
//
|
|
logEntrySize = sizeof(IO_ERROR_LOG_PACKET) + (11 * sizeof(WCHAR)) + (2 * (21 * sizeof(WCHAR)));
|
|
logEntrySize = min(logEntrySize, ERROR_LOG_MAXIMUM_SIZE);
|
|
logEntrySize = min(logEntrySize, MAXUCHAR);
|
|
|
|
errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(DeviceObject, (UCHAR)logEntrySize);
|
|
if (errorLogEntry != NULL)
|
|
{
|
|
//
|
|
// There are two event IDs we can use here. Both use the disk number,
|
|
// but one reports the available and used bytes while the other does not.
|
|
// We fall back on the latter if we failed to obtain the available and
|
|
// used byte counts from the LBP log page.
|
|
//
|
|
// The event insertion strings need to be in this order:
|
|
// 1. The disk number. (Both event IDs use this.)
|
|
// 2. Bytes used.
|
|
// 3. Bytes available.
|
|
//
|
|
|
|
RtlZeroMemory(errorLogEntry, logEntrySize);
|
|
errorLogEntry->StringOffset = sizeof(IO_ERROR_LOG_PACKET);
|
|
|
|
stringIndex = (PWCHAR)((ULONG_PTR)errorLogEntry + sizeof(IO_ERROR_LOG_PACKET));
|
|
stringSize = logEntrySize - sizeof(IO_ERROR_LOG_PACKET);
|
|
|
|
//
|
|
// Add the disk number to the insertion strings.
|
|
//
|
|
status = RtlStringCbPrintfW(stringIndex, stringSize, L"%d", fdoExtension->DeviceNumber);
|
|
|
|
if (NT_SUCCESS(status) )
|
|
{
|
|
errorLogEntry->NumberOfStrings++;
|
|
|
|
if (resources.UsedMappingResourcesValid &&
|
|
resources.AvailableMappingResourcesValid)
|
|
{
|
|
//
|
|
// Add the used mapping resources to the insertion strings.
|
|
//
|
|
stringIndex += (wcslen(stringIndex) + 1);
|
|
stringSize -= (LONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
|
|
|
|
status = RtlStringCbPrintfW(stringIndex, stringSize, L"%I64u", resources.UsedMappingResources);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
errorLogEntry->NumberOfStrings++;
|
|
|
|
//
|
|
// Add the available mapping resources to the insertion strings.
|
|
//
|
|
stringIndex += (wcslen(stringIndex) + 1);
|
|
stringSize -= (LONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
|
|
|
|
status = RtlStringCbPrintfW(stringIndex, stringSize, L"%I64u", resources.AvailableMappingResources);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
errorLogEntry->NumberOfStrings++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TracePrint((TRACE_LEVEL_WARNING,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogThresholdEvent: DO (%p), Used and available mapping resources were unavailable.\n",
|
|
DeviceObject));
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we were able to successfully assemble all 3 insertion strings,
|
|
// then we can use one of the "extended" event IDs. Otherwise, use the basic
|
|
// event ID, which only requires the disk number.
|
|
//
|
|
if (errorLogEntry->NumberOfStrings == 3)
|
|
{
|
|
if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN &&
|
|
resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN) {
|
|
|
|
errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_LUN_LUN;
|
|
|
|
} else if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN &&
|
|
resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN) {
|
|
|
|
errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_LUN_POOL;
|
|
|
|
} else if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN &&
|
|
resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN) {
|
|
|
|
errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_POOL_LUN;
|
|
|
|
} else if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN &&
|
|
resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN) {
|
|
|
|
errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_POOL_POOL;
|
|
|
|
} else {
|
|
|
|
errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED;
|
|
}
|
|
|
|
//
|
|
// Write the error log packet to the system error logging thread.
|
|
// It will be freed automatically.
|
|
//
|
|
IoWriteErrorLogEntry(errorLogEntry);
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogThresholdEvent: DO (%p), Soft threshold notification logged.\n",
|
|
DeviceObject));
|
|
}
|
|
else
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogThresholdEvent: DO (%p), Failed to allocate memory for error log entry.\n",
|
|
DeviceObject));
|
|
}
|
|
} else {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogThresholdEvent: DO (%p), Failed to allocate memory for SRB.\n",
|
|
DeviceObject));
|
|
}
|
|
|
|
|
|
//
|
|
// Clear the soft threshold event pending flag so that another can be queued.
|
|
//
|
|
InterlockedExchange((PLONG)&(fdoExtension->FunctionSupportInfo->LBProvisioningData.SoftThresholdEventPending), 0);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
|
|
|
|
FREE_POOL(srb);
|
|
|
|
if (workItem != NULL) {
|
|
IoFreeWorkItem(workItem);
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
ClasspLogSystemEventWithDeviceNumber(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ NTSTATUS IoErrorCode
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function is a helper routine to log any system events that require
|
|
the DeviceNumber (e.g. disk number). It is basically a wrapper for the
|
|
IoWriteErrorLogEntry call.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device for which the event needs to be logged.
|
|
IoErrorCode: The IO error code for the event.
|
|
|
|
Return Value:
|
|
STATUS_SUCCESS - if the event was logged
|
|
STATUS_INSUFFICIENT_RESOURCES - otherwise
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
PIO_ERROR_LOG_PACKET errorLogEntry = NULL;
|
|
ULONG logEntrySize = sizeof(IO_ERROR_LOG_PACKET);
|
|
PWCHAR stringIndex = NULL;
|
|
LONG stringSize = 0;
|
|
|
|
//
|
|
// We need to allocate enough space for one insertion string: a ULONG
|
|
// representing the disk number in decimal, which means a max of 10 digits,
|
|
// plus one for the NULL character.
|
|
// Make sure we do not exceed the max error log size or the max size of a
|
|
// UCHAR since the size gets truncated to a UCHAR when we pass it to
|
|
// IoAllocateErrorLogEntry().
|
|
//
|
|
logEntrySize = sizeof(IO_ERROR_LOG_PACKET) + (11 * sizeof(WCHAR));
|
|
logEntrySize = min(logEntrySize, ERROR_LOG_MAXIMUM_SIZE);
|
|
logEntrySize = min(logEntrySize, MAXUCHAR);
|
|
|
|
errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(DeviceObject, (UCHAR)logEntrySize);
|
|
if (errorLogEntry) {
|
|
|
|
RtlZeroMemory(errorLogEntry, logEntrySize);
|
|
errorLogEntry->StringOffset = sizeof(IO_ERROR_LOG_PACKET);
|
|
errorLogEntry->ErrorCode = IoErrorCode;
|
|
|
|
stringIndex = (PWCHAR)((ULONG_PTR)errorLogEntry + sizeof(IO_ERROR_LOG_PACKET));
|
|
stringSize = logEntrySize - sizeof(IO_ERROR_LOG_PACKET);
|
|
|
|
//
|
|
// Add the disk number to the insertion strings.
|
|
//
|
|
status = RtlStringCbPrintfW(stringIndex, stringSize, L"%d", fdoExtension->DeviceNumber);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
errorLogEntry->NumberOfStrings++;
|
|
}
|
|
|
|
//
|
|
// Write the error log packet to the system error logging thread.
|
|
// It will be freed automatically.
|
|
//
|
|
IoWriteErrorLogEntry(errorLogEntry);
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
_Function_class_(IO_WORKITEM_ROUTINE)
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
VOID
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClassLogResourceExhaustionEvent(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function logs a logical block provisioning permanent resource exhaustion
|
|
event to the system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the permanent
|
|
resource exhaustion.
|
|
Context: A pointer to the IO_WORKITEM in which this function is running.
|
|
|
|
--*/
|
|
{
|
|
PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
|
|
|
|
if (NT_SUCCESS(ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_ERROR_DISK_RESOURCES_EXHAUSTED))) {
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogResourceExhaustionEvent: DO (%p), Permanent resource exhaustion logged.\n",
|
|
DeviceObject));
|
|
} else {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogResourceExhaustionEvent: DO (%p), Failed to allocate memory for error log entry.\n",
|
|
DeviceObject));
|
|
}
|
|
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
|
|
|
|
if (workItem != NULL) {
|
|
IoFreeWorkItem(workItem);
|
|
}
|
|
}
|
|
|
|
|
|
VOID ClassQueueThresholdEventWorker(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function queues a delayed work item that will eventually log a
|
|
logical block provisioning soft threshold event to the system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the soft
|
|
threshold.
|
|
|
|
--*/
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
|
|
PIO_WORKITEM workItem = NULL;
|
|
|
|
if (commonExtension->IsFdo &&
|
|
InterlockedCompareExchange((PLONG)&(fdoExtension->FunctionSupportInfo->LBProvisioningData.SoftThresholdEventPending), 1, 0) == 0)
|
|
{
|
|
workItem = IoAllocateWorkItem(DeviceObject);
|
|
|
|
if (workItem)
|
|
{
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueThresholdEventWorker: DO (%p), Queueing soft threshold notification work item.\n",
|
|
DeviceObject));
|
|
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
|
|
|
|
//
|
|
// Queue a work item to write the threshold notification to the
|
|
// system event log.
|
|
//
|
|
IoQueueWorkItem(workItem, ClassLogThresholdEvent, DelayedWorkQueue, workItem);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Clear the soft threshold event pending flag since this is normally
|
|
// done when the work item completes.
|
|
//
|
|
InterlockedExchange((PLONG)&(fdoExtension->FunctionSupportInfo->LBProvisioningData.SoftThresholdEventPending), 0);
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueThresholdEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
|
|
DeviceObject));
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID ClassQueueResourceExhaustionEventWorker(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function queues a delayed work item that will eventually log a
|
|
logical block provisioning permanent resource exhaustion event to the
|
|
system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the resource
|
|
exhaustion.
|
|
|
|
--*/
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
|
|
PIO_WORKITEM workItem = NULL;
|
|
|
|
if (commonExtension->IsFdo)
|
|
{
|
|
workItem = IoAllocateWorkItem(DeviceObject);
|
|
|
|
if (workItem)
|
|
{
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueResourceExhaustionEventWorker: DO (%p), Queueing permanent resource exhaustion event work item.\n",
|
|
DeviceObject));
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
|
|
|
|
//
|
|
// Queue a work item to write the threshold notification to the
|
|
// system event log.
|
|
//
|
|
IoQueueWorkItem(workItem, ClassLogResourceExhaustionEvent, DelayedWorkQueue, workItem);
|
|
}
|
|
else
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueResourceExhaustionEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
|
|
DeviceObject));
|
|
}
|
|
}
|
|
}
|
|
|
|
_Function_class_(IO_WORKITEM_ROUTINE)
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
VOID
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClassLogCapacityChangedProcess(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function logs a capacity changed event to the system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the capacity change.
|
|
Context: A pointer to the IO_WORKITEM in which this function is running.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
|
|
|
|
status = ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_WARNING_DISK_CAPACITY_CHANGED);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogCapacityChangedEvent: DO (%p), Capacity changed logged.\n",
|
|
DeviceObject));
|
|
} else {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogCapacityChangedEvent: DO (%p), Failed to allocate memory for error log entry.\n",
|
|
DeviceObject));
|
|
}
|
|
|
|
//
|
|
// Get disk capacity and notify upper layer if capacity is changed.
|
|
//
|
|
status = ClassReadDriveCapacity(DeviceObject);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogCapacityChangedEvent: DO (%p), ClassReadDriveCapacity returned %!STATUS!.\n",
|
|
DeviceObject,
|
|
status));
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
|
|
|
|
if (workItem != NULL) {
|
|
IoFreeWorkItem(workItem);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ClassQueueCapacityChangedEventWorker(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function queues a delayed work item that will eventually log a
|
|
disk capacity changed event to the system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the capacity change.
|
|
|
|
--*/
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
|
|
PIO_WORKITEM workItem = NULL;
|
|
|
|
if (commonExtension->IsFdo)
|
|
{
|
|
workItem = IoAllocateWorkItem(DeviceObject);
|
|
|
|
if (workItem)
|
|
{
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueCapacityChangedEventWorker: DO (%p), Queueing capacity changed event work item.\n",
|
|
DeviceObject));
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
|
|
|
|
//
|
|
// Queue a work item to write the threshold notification to the
|
|
// system event log.
|
|
//
|
|
IoQueueWorkItem(workItem, ClassLogCapacityChangedProcess, DelayedWorkQueue, workItem);
|
|
}
|
|
else
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueCapacityChangedEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
|
|
DeviceObject));
|
|
}
|
|
}
|
|
}
|
|
|
|
_Function_class_(IO_WORKITEM_ROUTINE)
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
VOID
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClassLogProvisioningTypeChangedEvent(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PVOID Context
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function logs a provisioning type changed event to the system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the provisioning type change.
|
|
Context: A pointer to the IO_WORKITEM in which this function is running.
|
|
|
|
--*/
|
|
{
|
|
PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
|
|
|
|
if (NT_SUCCESS(ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_WARNING_DISK_PROVISIONING_TYPE_CHANGED))) {
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogProvisioningTypeChangedEvent: DO (%p), LB Provisioning Type changed logged.\n",
|
|
DeviceObject));
|
|
} else {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassLogProvisioningTypeChangedEvent: DO (%p), Failed to allocate memory for error log entry.\n",
|
|
DeviceObject));
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
|
|
|
|
IoFreeWorkItem(workItem);
|
|
}
|
|
|
|
|
|
VOID
|
|
ClassQueueProvisioningTypeChangedEventWorker(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function queues a delayed work item that will eventually log a
|
|
provisioning type changed event to the system event log.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that reported the provisioning type change.
|
|
|
|
--*/
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
|
|
PIO_WORKITEM workItem = NULL;
|
|
|
|
if (commonExtension->IsFdo)
|
|
{
|
|
workItem = IoAllocateWorkItem(DeviceObject);
|
|
|
|
if (workItem)
|
|
{
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueProvisioningTypeChangedEventWorker: DO (%p), Queueing LB provisioning type changed event work item.\n",
|
|
DeviceObject));
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
|
|
|
|
//
|
|
// Queue a work item to write the threshold notification to the
|
|
// system event log.
|
|
//
|
|
IoQueueWorkItem(workItem, ClassLogProvisioningTypeChangedEvent, DelayedWorkQueue, workItem);
|
|
}
|
|
else
|
|
{
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClassQueueProvisioningTypeChangedEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
|
|
DeviceObject));
|
|
}
|
|
}
|
|
}
|
|
|
|
_Function_class_(IO_WORKITEM_ROUTINE)
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
VOID
|
|
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
|
ClasspLogIOEventWithContext(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function logs an event to the system event log with dumpdata containing opcode and
|
|
sense data.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that retried the IO.
|
|
Context: A pointer to the OPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT that has data to be logged as part of the message.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER ioLogMessageContextHeader = (POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER)Context;
|
|
PIO_WORKITEM workItem;
|
|
PIO_ERROR_LOG_PACKET errorLogEntry = NULL;
|
|
ULONG logEntrySize;
|
|
PWCHAR stringIndex = NULL;
|
|
LONG stringSize = 0;
|
|
ULONG senseBufferSize;
|
|
ULONG stringsBufferLength = 0;
|
|
ULONG pdoNameLength = 0;
|
|
|
|
NT_ASSERT(ioLogMessageContextHeader != NULL);
|
|
_Analysis_assume_(ioLogMessageContextHeader != NULL);
|
|
|
|
switch (ioLogMessageContextHeader->ErrorCode) {
|
|
|
|
case IO_ERROR_IO_HARDWARE_ERROR:
|
|
case IO_WARNING_IO_OPERATION_RETRIED: {
|
|
|
|
//
|
|
// We need to allocate enough space for 3 insertion strings:
|
|
// 1. A ULONGLONG in Hex representing the LBA which means a max of 16 digits,
|
|
// plus two for "0x" plus one for the NULL character.
|
|
// 2. A ULONG representing the disk number in decimal, which means
|
|
// a max of 10 digits, plus one for the NULL character.
|
|
// 3. The PDO name, so that if the disk number is hidden from the
|
|
// user for some reason, there is still a way to associate the
|
|
// event with the correct device.
|
|
//
|
|
stringsBufferLength = (19 + 11) * sizeof(WCHAR);
|
|
|
|
//
|
|
// Query for the size of the PDO name.
|
|
//
|
|
status = IoGetDeviceProperty(fdoExtension->LowerPdo,
|
|
DevicePropertyPhysicalDeviceObjectName,
|
|
0,
|
|
NULL,
|
|
&pdoNameLength);
|
|
|
|
if (status == STATUS_BUFFER_TOO_SMALL && pdoNameLength > 0) {
|
|
stringsBufferLength += pdoNameLength;
|
|
} else {
|
|
pdoNameLength = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
workItem = ioLogMessageContextHeader->WorkItem;
|
|
|
|
//
|
|
// DumpData[0] which is of ULONG size and will contain opcode|srbstatus|scsistatus.
|
|
// Then we will have sensebuffer, hence
|
|
// DumpDataSize = senseBufferSize + sizeof(ULONG)
|
|
// and DumpDataSize must be multiple of sizeof(ULONG)
|
|
// which means senseBufferSize needs to ULONG aligned
|
|
// Please note we will use original buffersize for padding later
|
|
//
|
|
senseBufferSize = ALIGN_UP_BY(ioLogMessageContextHeader->SenseDataSize, sizeof(ULONG));
|
|
|
|
logEntrySize = FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ) + sizeof(ULONG) + senseBufferSize;
|
|
|
|
//
|
|
// We need to make sure the string offset is WCHAR-aligned (the insertion strings
|
|
// come after the sense buffer in the dump data, if any).
|
|
// But we don't need to do anything special for it,
|
|
// since FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData) is currently ULONG aligned
|
|
// and SenseBufferSize is also ULONG aligned. This means buffer that precedes the insertion string is ULONG aligned
|
|
// note stringoffset = FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ) + DumpDataSize
|
|
// This leads us to fact that stringoffset will always be ULONG aligned and effectively WCHAR aligned
|
|
//
|
|
|
|
//
|
|
// We need to allocate enough space for the insertion strings provided in the passed in Context
|
|
// as well as the opcode and the sense data, while making sure we cap at max error log size.
|
|
// The log packet is followed by the opcode, then the sense data, and then the
|
|
// insertion strings.
|
|
//
|
|
logEntrySize = logEntrySize + stringsBufferLength;
|
|
|
|
if (logEntrySize > ERROR_LOG_MAXIMUM_SIZE) {
|
|
if (senseBufferSize) {
|
|
if (logEntrySize - ERROR_LOG_MAXIMUM_SIZE < senseBufferSize) {
|
|
//
|
|
// In below steps, senseBufferSize will become same or less than as ioLogMessageContextHeader->SenseDataSize
|
|
// it can't be more than that.
|
|
//
|
|
senseBufferSize -= logEntrySize - ERROR_LOG_MAXIMUM_SIZE;
|
|
|
|
//
|
|
// Decrease the sensebuffersize further, if needed, to keep senseBufferSize ULONG aligned
|
|
//
|
|
senseBufferSize = ALIGN_DOWN_BY(senseBufferSize, sizeof(ULONG));
|
|
|
|
} else {
|
|
senseBufferSize = 0;
|
|
}
|
|
}
|
|
logEntrySize = ERROR_LOG_MAXIMUM_SIZE;
|
|
}
|
|
|
|
errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(DeviceObject, (UCHAR)logEntrySize);
|
|
|
|
if (errorLogEntry) {
|
|
|
|
RtlZeroMemory(errorLogEntry, logEntrySize);
|
|
errorLogEntry->MajorFunctionCode = IRP_MJ_SCSI;
|
|
errorLogEntry->RetryCount = 1;
|
|
errorLogEntry->DumpDataSize = (USHORT)(sizeof(ULONG) + senseBufferSize);
|
|
errorLogEntry->StringOffset = (USHORT)(FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ) + errorLogEntry->DumpDataSize);
|
|
errorLogEntry->ErrorCode = ioLogMessageContextHeader->ErrorCode;
|
|
errorLogEntry->DumpData[0] = (((ULONG)(ioLogMessageContextHeader->OpCode)) << 24) |
|
|
(((ULONG)(ioLogMessageContextHeader->SrbStatus)) << 16) |
|
|
(((ULONG)(ioLogMessageContextHeader->ScsiStatus)) << 8);
|
|
|
|
//
|
|
// Copy sense data and do padding for sense data if needed, with '-'
|
|
//
|
|
if (senseBufferSize > ioLogMessageContextHeader->SenseDataSize) {
|
|
RtlCopyMemory(&errorLogEntry->DumpData[1], ioLogMessageContextHeader->SenseData, ioLogMessageContextHeader->SenseDataSize);
|
|
RtlFillMemory( (PCHAR)&errorLogEntry->DumpData[1] + ioLogMessageContextHeader->SenseDataSize , (senseBufferSize - ioLogMessageContextHeader->SenseDataSize) , '-' );
|
|
} else {
|
|
RtlCopyMemory(&errorLogEntry->DumpData[1], ioLogMessageContextHeader->SenseData, senseBufferSize);
|
|
}
|
|
|
|
stringIndex = (PWCHAR)((PCHAR)errorLogEntry->DumpData + errorLogEntry->DumpDataSize);
|
|
stringSize = logEntrySize - errorLogEntry->StringOffset;
|
|
|
|
//
|
|
// Add the strings
|
|
//
|
|
switch (ioLogMessageContextHeader->ErrorCode) {
|
|
case IO_ERROR_IO_HARDWARE_ERROR:
|
|
case IO_WARNING_IO_OPERATION_RETRIED: {
|
|
|
|
PIO_RETRIED_LOG_MESSAGE_CONTEXT ioLogMessageContext = (PIO_RETRIED_LOG_MESSAGE_CONTEXT)Context;
|
|
|
|
//
|
|
// The first is a "0x" plus ULONGLONG in hex representing the LBA plus the NULL character.
|
|
// The second is a ULONG representing the disk number plus the NULL character.
|
|
//
|
|
status = RtlStringCbPrintfW(stringIndex, stringSize, L"0x%I64x", ioLogMessageContext->Lba.QuadPart);
|
|
if (NT_SUCCESS(status)) {
|
|
errorLogEntry->NumberOfStrings++;
|
|
|
|
//
|
|
// Add the disk number to the insertion strings.
|
|
//
|
|
stringSize -= (ULONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
|
|
stringIndex += (wcslen(stringIndex) + 1);
|
|
|
|
if (stringSize > 0) {
|
|
|
|
status = RtlStringCbPrintfW(stringIndex, stringSize, L"%d", ioLogMessageContext->DeviceNumber);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
errorLogEntry->NumberOfStrings++;
|
|
|
|
stringSize -= (ULONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
|
|
stringIndex += (wcslen(stringIndex) + 1);
|
|
|
|
if (stringSize >= (LONG)pdoNameLength && pdoNameLength > 0) {
|
|
ULONG resultLength;
|
|
|
|
//
|
|
// Get the PDO name and place it in the insertion string buffer.
|
|
//
|
|
status = IoGetDeviceProperty(fdoExtension->LowerPdo,
|
|
DevicePropertyPhysicalDeviceObjectName,
|
|
pdoNameLength,
|
|
stringIndex,
|
|
&resultLength);
|
|
|
|
if (NT_SUCCESS(status) && resultLength > 0) {
|
|
errorLogEntry->NumberOfStrings++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Write the error log packet to the system error logging thread.
|
|
// It will be freed automatically.
|
|
//
|
|
IoWriteErrorLogEntry(errorLogEntry);
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClasspLogIORetriedEvent: DO (%p), Soft threshold notification logged.\n",
|
|
DeviceObject));
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
|
|
|
|
if (ioLogMessageContextHeader->SenseData) {
|
|
ExFreePool(ioLogMessageContextHeader->SenseData);
|
|
}
|
|
if (workItem) {
|
|
IoFreeWorkItem(workItem);
|
|
}
|
|
ExFreePool(ioLogMessageContextHeader);
|
|
}
|
|
|
|
|
|
VOID
|
|
ClasspQueueLogIOEventWithContextWorker(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ ULONG SenseBufferSize,
|
|
_In_ PVOID SenseData,
|
|
_In_ UCHAR SrbStatus,
|
|
_In_ UCHAR ScsiStatus,
|
|
_In_ ULONG ErrorCode,
|
|
_In_ ULONG CdbLength,
|
|
_In_opt_ PCDB Cdb,
|
|
_In_opt_ PTRANSFER_PACKET Pkt
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
Helper function that queues a delayed work item that will eventually
|
|
log an event to the system event log corresponding to passed in ErrorCode.
|
|
The dumpdata is fixed to include the opcode and the sense information.
|
|
But the number of insertion strings varies based on the passed in ErrorCode.
|
|
|
|
Arguments:
|
|
DeviceObject: The FDO that represents the device that was the target of the IO.
|
|
SesneBufferSize: Size of the SenseData buffer.
|
|
SenseData: Error information from the target (to be included in the dump data).
|
|
SrbStatus: Srb status returned by the miniport.
|
|
ScsiStatus: SCSI status associated with the request upon completion from lower layers.
|
|
ErrorCode: Numerical value of the error code.
|
|
CdbLength: Number of bytes of Cdb.
|
|
Cdb: Pointer to the CDB.
|
|
Pkt: The tranfer packet representing the IO of interest. This may be NULL.
|
|
|
|
--*/
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER ioLogMessageContextHeader = NULL;
|
|
PVOID senseData = NULL;
|
|
PIO_WORKITEM workItem = NULL;
|
|
ULONG senseBufferSize = 0;
|
|
LARGE_INTEGER lba = {0};
|
|
|
|
if (!commonExtension->IsFdo) {
|
|
return;
|
|
}
|
|
|
|
if (!Cdb) {
|
|
return;
|
|
}
|
|
|
|
workItem = IoAllocateWorkItem(DeviceObject);
|
|
if (!workItem) {
|
|
goto __ClasspQueueLogIOEventWithContextWorker_ExitWithMessage;
|
|
}
|
|
|
|
if (SenseBufferSize) {
|
|
senseData = ExAllocatePoolWithTag(NonPagedPoolNx, SenseBufferSize, CLASSPNP_POOL_TAG_LOG_MESSAGE);
|
|
if (senseData) {
|
|
senseBufferSize = SenseBufferSize;
|
|
}
|
|
}
|
|
|
|
if (CdbLength == 16) {
|
|
REVERSE_BYTES_QUAD(&lba, Cdb->CDB16.LogicalBlock);
|
|
} else {
|
|
((PFOUR_BYTE)&lba.LowPart)->Byte3 = Cdb->CDB10.LogicalBlockByte0;
|
|
((PFOUR_BYTE)&lba.LowPart)->Byte2 = Cdb->CDB10.LogicalBlockByte1;
|
|
((PFOUR_BYTE)&lba.LowPart)->Byte1 = Cdb->CDB10.LogicalBlockByte2;
|
|
((PFOUR_BYTE)&lba.LowPart)->Byte0 = Cdb->CDB10.LogicalBlockByte3;
|
|
}
|
|
|
|
//
|
|
// Calculate the amount of buffer required for the insertion strings.
|
|
//
|
|
switch (ErrorCode) {
|
|
case IO_ERROR_IO_HARDWARE_ERROR:
|
|
case IO_WARNING_IO_OPERATION_RETRIED: {
|
|
|
|
PIO_RETRIED_LOG_MESSAGE_CONTEXT ioLogMessageContext = NULL;
|
|
|
|
ioLogMessageContext = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(IO_RETRIED_LOG_MESSAGE_CONTEXT), CLASSPNP_POOL_TAG_LOG_MESSAGE);
|
|
if (!ioLogMessageContext) {
|
|
goto __ClasspQueueLogIOEventWithContextWorker_ExitWithMessage;
|
|
}
|
|
|
|
ioLogMessageContext->Lba.QuadPart = lba.QuadPart;
|
|
ioLogMessageContext->DeviceNumber = fdoExtension->DeviceNumber;
|
|
|
|
ioLogMessageContextHeader = (POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER)ioLogMessageContext;
|
|
|
|
break;
|
|
}
|
|
|
|
default: goto __ClasspQueueLogIOEventWithContextWorker_Exit;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClasspQueueLogIOEventWithContextWorker: DO (%p), Pkt (%p), Queueing IO retried event log message work item.\n",
|
|
DeviceObject,
|
|
Pkt));
|
|
|
|
ioLogMessageContextHeader->WorkItem = workItem;
|
|
if (senseData) {
|
|
RtlCopyMemory(senseData, SenseData, SenseBufferSize);
|
|
}
|
|
ioLogMessageContextHeader->SenseData = senseData;
|
|
ioLogMessageContextHeader->SenseDataSize = senseBufferSize;
|
|
ioLogMessageContextHeader->SrbStatus = SrbStatus;
|
|
ioLogMessageContextHeader->ScsiStatus = ScsiStatus;
|
|
ioLogMessageContextHeader->OpCode = Cdb->CDB6GENERIC.OperationCode;
|
|
ioLogMessageContextHeader->Reserved = 0;
|
|
ioLogMessageContextHeader->ErrorCode = ErrorCode;
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
|
|
|
|
//
|
|
// Queue a work item to write the system event log.
|
|
//
|
|
IoQueueWorkItem(workItem, ClasspLogIOEventWithContext, DelayedWorkQueue, ioLogMessageContextHeader);
|
|
|
|
return;
|
|
|
|
__ClasspQueueLogIOEventWithContextWorker_ExitWithMessage:
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_GENERAL,
|
|
"ClasspQueueLogIOEventWithContextWorker: DO (%p), Failed to allocate memory for the log message.\n",
|
|
DeviceObject));
|
|
|
|
__ClasspQueueLogIOEventWithContextWorker_Exit:
|
|
if (senseData) {
|
|
ExFreePool(senseData);
|
|
}
|
|
if (workItem) {
|
|
IoFreeWorkItem(workItem);
|
|
}
|
|
if (ioLogMessageContextHeader) {
|
|
ExFreePool(ioLogMessageContextHeader);
|
|
}
|
|
}
|
|
|
|
static
|
|
BOOLEAN
|
|
ValidPersistentReserveScope(
|
|
UCHAR Scope)
|
|
{
|
|
switch (Scope) {
|
|
case RESERVATION_SCOPE_LU:
|
|
case RESERVATION_SCOPE_ELEMENT:
|
|
|
|
return TRUE;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static
|
|
BOOLEAN
|
|
ValidPersistentReserveType(
|
|
UCHAR Type)
|
|
{
|
|
switch (Type) {
|
|
case RESERVATION_TYPE_WRITE_EXCLUSIVE:
|
|
case RESERVATION_TYPE_EXCLUSIVE:
|
|
case RESERVATION_TYPE_WRITE_EXCLUSIVE_REGISTRANTS:
|
|
case RESERVATION_TYPE_EXCLUSIVE_REGISTRANTS:
|
|
|
|
return TRUE;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
ClasspPersistentReserve
|
|
|
|
Routine Description:
|
|
|
|
Handles IOCTL_STORAGE_PERSISTENT_RESERVE_IN and IOCTL_STORAGE_PERSISTENT_RESERVE_OUT.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
Irp - a pointer to the I/O request packet
|
|
Srb - pointer to preallocated SCSI_REQUEST_BLOCK.
|
|
|
|
Return Value:
|
|
|
|
Status Code
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspPersistentReserve(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
{
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PCDB cdb = NULL;
|
|
PPERSISTENT_RESERVE_COMMAND prCommand = Irp->AssociatedIrp.SystemBuffer;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
NTSTATUS status;
|
|
|
|
ULONG dataBufLen;
|
|
ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
|
|
|
|
BOOLEAN writeToDevice;
|
|
|
|
//
|
|
// Check common input buffer parameters.
|
|
//
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
sizeof(PERSISTENT_RESERVE_COMMAND) ||
|
|
prCommand->Size < sizeof(PERSISTENT_RESERVE_COMMAND)) {
|
|
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
Irp->IoStatus.Status = status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
FREE_POOL(Srb);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
goto ClasspPersistentReserve_Exit;
|
|
}
|
|
|
|
//
|
|
// Check buffer alignment. Only an issue if another kernel mode component
|
|
// (not the I/O manager) allocates the buffer.
|
|
//
|
|
|
|
if ((ULONG_PTR)prCommand & fdoExtension->AdapterDescriptor->AlignmentMask) {
|
|
|
|
status = STATUS_INVALID_USER_BUFFER;
|
|
Irp->IoStatus.Status = status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
FREE_POOL(Srb);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
goto ClasspPersistentReserve_Exit;
|
|
}
|
|
|
|
//
|
|
// Check additional parameters.
|
|
//
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
SrbSetCdbLength(Srb, 10);
|
|
cdb = SrbGetCdb(Srb);
|
|
|
|
if (controlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_IN) {
|
|
|
|
//
|
|
// Check output buffer for PR In.
|
|
//
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
prCommand->PR_IN.AllocationLength) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
switch (prCommand->PR_IN.ServiceAction) {
|
|
|
|
case RESERVATION_ACTION_READ_KEYS:
|
|
|
|
if (prCommand->PR_IN.AllocationLength < sizeof(PRI_REGISTRATION_LIST)) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
break;
|
|
|
|
case RESERVATION_ACTION_READ_RESERVATIONS:
|
|
|
|
if (prCommand->PR_IN.AllocationLength < sizeof(PRI_RESERVATION_LIST)) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
Irp->IoStatus.Status = status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
FREE_POOL(Srb);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
goto ClasspPersistentReserve_Exit;
|
|
}
|
|
|
|
//
|
|
// Fill in the CDB.
|
|
//
|
|
|
|
cdb->PERSISTENT_RESERVE_IN.OperationCode = SCSIOP_PERSISTENT_RESERVE_IN;
|
|
cdb->PERSISTENT_RESERVE_IN.ServiceAction = prCommand->PR_IN.ServiceAction;
|
|
|
|
REVERSE_BYTES_SHORT(&(cdb->PERSISTENT_RESERVE_IN.AllocationLength),
|
|
&(prCommand->PR_IN.AllocationLength));
|
|
|
|
dataBufLen = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
writeToDevice = FALSE;
|
|
|
|
|
|
} else {
|
|
|
|
//
|
|
// Verify ServiceAction, Scope, and Type
|
|
//
|
|
|
|
switch (prCommand->PR_OUT.ServiceAction) {
|
|
|
|
case RESERVATION_ACTION_REGISTER:
|
|
case RESERVATION_ACTION_REGISTER_IGNORE_EXISTING:
|
|
case RESERVATION_ACTION_CLEAR:
|
|
|
|
// Scope and type ignored.
|
|
|
|
break;
|
|
|
|
case RESERVATION_ACTION_RESERVE:
|
|
case RESERVATION_ACTION_RELEASE:
|
|
case RESERVATION_ACTION_PREEMPT:
|
|
case RESERVATION_ACTION_PREEMPT_ABORT:
|
|
|
|
if (!ValidPersistentReserveScope(prCommand->PR_OUT.Scope) ||
|
|
!ValidPersistentReserveType(prCommand->PR_OUT.Type)) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check input buffer for PR Out.
|
|
// Caller must include the PR parameter list.
|
|
//
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
(sizeof(PERSISTENT_RESERVE_COMMAND) +
|
|
sizeof(PRO_PARAMETER_LIST)) ||
|
|
prCommand->Size <
|
|
irpStack->Parameters.DeviceIoControl.InputBufferLength) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
Irp->IoStatus.Status = status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
FREE_POOL(Srb);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
goto ClasspPersistentReserve_Exit;
|
|
}
|
|
|
|
//
|
|
// Fill in the CDB.
|
|
//
|
|
|
|
cdb->PERSISTENT_RESERVE_OUT.OperationCode = SCSIOP_PERSISTENT_RESERVE_OUT;
|
|
cdb->PERSISTENT_RESERVE_OUT.ServiceAction = prCommand->PR_OUT.ServiceAction;
|
|
cdb->PERSISTENT_RESERVE_OUT.Scope = prCommand->PR_OUT.Scope;
|
|
cdb->PERSISTENT_RESERVE_OUT.Type = prCommand->PR_OUT.Type;
|
|
|
|
cdb->PERSISTENT_RESERVE_OUT.ParameterListLength[1] = (UCHAR)sizeof(PRO_PARAMETER_LIST);
|
|
|
|
//
|
|
// Move the parameter list to the beginning of the data buffer (so it is aligned
|
|
// correctly and that the MDL describes it correctly).
|
|
//
|
|
|
|
RtlMoveMemory(prCommand,
|
|
prCommand->PR_OUT.ParameterList,
|
|
sizeof(PRO_PARAMETER_LIST));
|
|
|
|
dataBufLen = sizeof(PRO_PARAMETER_LIST);
|
|
writeToDevice = TRUE;
|
|
}
|
|
|
|
//
|
|
// Fill in the SRB
|
|
//
|
|
|
|
//
|
|
// Set timeout value.
|
|
//
|
|
|
|
SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
|
|
|
|
//
|
|
// Send as a tagged request.
|
|
//
|
|
|
|
SrbSetRequestAttribute(Srb, SRB_HEAD_OF_QUEUE_TAG_REQUEST);
|
|
SrbSetSrbFlags(Srb, SRB_FLAGS_NO_QUEUE_FREEZE | SRB_FLAGS_QUEUE_ACTION_ENABLE);
|
|
|
|
status = ClassSendSrbAsynchronous(DeviceObject,
|
|
Srb,
|
|
Irp,
|
|
prCommand,
|
|
dataBufLen,
|
|
writeToDevice);
|
|
|
|
ClasspPersistentReserve_Exit:
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
/*++
|
|
|
|
ClasspPriorityHint
|
|
|
|
Routine Description:
|
|
|
|
Handles IOCTL_STORAGE_CHECK_PRIORITY_HINT_SUPPORT.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
Irp - a pointer to the I/O request packet
|
|
|
|
Return Value:
|
|
|
|
Status Code
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspPriorityHint(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
|
|
PSTORAGE_PRIORITY_HINT_SUPPORT priSupport = Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
//
|
|
// Check whether this device supports idle priority.
|
|
//
|
|
if (!fdoData->IdlePrioritySupported) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto PriorityHintExit;
|
|
}
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(STORAGE_PRIORITY_HINT_SUPPORT)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto PriorityHintExit;
|
|
}
|
|
|
|
RtlZeroMemory(priSupport, sizeof(STORAGE_PRIORITY_HINT_SUPPORT));
|
|
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
if (!NT_SUCCESS(status)) {
|
|
//
|
|
// If I/O priority is not supported by lower drivers, just set the
|
|
// priorities supported by class driver.
|
|
//
|
|
TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_IOCTL, "ClasspPriorityHint: I/O priority not supported by port driver.\n"));
|
|
priSupport->SupportFlags = 0;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_IOCTL, "ClasspPriorityHint: I/O priorities supported by port driver: %X\n", priSupport->SupportFlags));
|
|
|
|
priSupport->SupportFlags |= (1 << IoPriorityVeryLow) |
|
|
(1 << IoPriorityLow) |
|
|
(1 << IoPriorityNormal) ;
|
|
|
|
TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_IOCTL, "ClasspPriorityHint: I/O priorities supported: %X\n", priSupport->SupportFlags));
|
|
Irp->IoStatus.Information = sizeof(STORAGE_PRIORITY_HINT_SUPPORT);
|
|
|
|
PriorityHintExit:
|
|
|
|
Irp->IoStatus.Status = status;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
return status;
|
|
}
|
|
|
|
/*++
|
|
|
|
ClasspConvertToScsiRequestBlock
|
|
|
|
Routine Description:
|
|
|
|
Convert an extended SRB to a SCSI_REQUEST_BLOCK. This function handles only
|
|
a single SRB and will not converted SRBs that are linked.
|
|
|
|
Arguments:
|
|
|
|
Srb - a pointer to a SCSI_REQUEST_BLOCK
|
|
SrbEx - a pointer to an extended SRB
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
VOID
|
|
ClasspConvertToScsiRequestBlock(
|
|
_Out_ PSCSI_REQUEST_BLOCK Srb,
|
|
_In_ PSTORAGE_REQUEST_BLOCK SrbEx
|
|
)
|
|
{
|
|
PSTOR_ADDR_BTL8 storAddrBtl8;
|
|
ULONG i;
|
|
BOOLEAN foundEntry = FALSE;
|
|
PSRBEX_DATA srbExData;
|
|
|
|
if ((Srb == NULL) || (SrbEx == NULL)) {
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
Srb->Function = (UCHAR)SrbEx->SrbFunction;
|
|
Srb->SrbStatus = SrbEx->SrbStatus;
|
|
Srb->QueueTag = (UCHAR)SrbEx->RequestTag;
|
|
Srb->QueueAction = (UCHAR)SrbEx->RequestAttribute;
|
|
Srb->SrbFlags = SrbEx->SrbFlags;
|
|
Srb->DataTransferLength = SrbEx->DataTransferLength;
|
|
Srb->TimeOutValue = SrbEx->TimeOutValue;
|
|
Srb->DataBuffer = SrbEx->DataBuffer;
|
|
Srb->OriginalRequest = SrbEx->OriginalRequest;
|
|
Srb->SrbExtension = SrbEx->MiniportContext;
|
|
Srb->InternalStatus = SrbEx->SystemStatus;
|
|
|
|
//
|
|
// Handle address fields
|
|
//
|
|
if (SrbEx->AddressOffset >= sizeof(STORAGE_REQUEST_BLOCK)) {
|
|
storAddrBtl8 = (PSTOR_ADDR_BTL8)((PCHAR)SrbEx + SrbEx->AddressOffset);
|
|
|
|
if (storAddrBtl8->Type == STOR_ADDRESS_TYPE_BTL8) {
|
|
Srb->PathId = storAddrBtl8->Path;
|
|
Srb->TargetId = storAddrBtl8->Target;
|
|
Srb->Lun = storAddrBtl8->Lun;
|
|
} else {
|
|
// Catch unsupported address types
|
|
NT_ASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Handle SRB function specific fields
|
|
//
|
|
if (SrbEx->NumSrbExData > 0) {
|
|
|
|
for (i = 0; i < SrbEx->NumSrbExData; i++) {
|
|
|
|
if ((SrbEx->SrbExDataOffset[i] == 0) ||
|
|
(SrbEx->SrbExDataOffset[i] < sizeof(STORAGE_REQUEST_BLOCK))) {
|
|
// Catch invalid offsets
|
|
NT_ASSERT(FALSE);
|
|
continue;
|
|
}
|
|
|
|
srbExData = (PSRBEX_DATA)((PCHAR)SrbEx + SrbEx->SrbExDataOffset[i]);
|
|
|
|
switch (SrbEx->SrbFunction) {
|
|
|
|
case SRB_FUNCTION_EXECUTE_SCSI:
|
|
|
|
switch (srbExData->Type) {
|
|
|
|
case SrbExDataTypeScsiCdb16:
|
|
Srb->ScsiStatus = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->ScsiStatus;
|
|
Srb->CdbLength = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->CdbLength;
|
|
Srb->SenseInfoBufferLength = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->SenseInfoBufferLength;
|
|
Srb->SenseInfoBuffer = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->SenseInfoBuffer;
|
|
RtlCopyMemory(Srb->Cdb, ((PSRBEX_DATA_SCSI_CDB16)srbExData)->Cdb, sizeof(Srb->Cdb));
|
|
foundEntry = TRUE;
|
|
break;
|
|
|
|
case SrbExDataTypeScsiCdb32:
|
|
Srb->ScsiStatus = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->ScsiStatus;
|
|
Srb->CdbLength = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->CdbLength;
|
|
Srb->SenseInfoBufferLength = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->SenseInfoBufferLength;
|
|
Srb->SenseInfoBuffer = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->SenseInfoBuffer;
|
|
|
|
// Copy only the first 16 bytes
|
|
RtlCopyMemory(Srb->Cdb, ((PSRBEX_DATA_SCSI_CDB32)srbExData)->Cdb, sizeof(Srb->Cdb));
|
|
foundEntry = TRUE;
|
|
break;
|
|
|
|
case SrbExDataTypeScsiCdbVar:
|
|
Srb->ScsiStatus = ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->ScsiStatus;
|
|
Srb->CdbLength = (UCHAR)((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->CdbLength;
|
|
Srb->SenseInfoBufferLength = ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->SenseInfoBufferLength;
|
|
Srb->SenseInfoBuffer = ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->SenseInfoBuffer;
|
|
|
|
// Copy only the first 16 bytes
|
|
RtlCopyMemory(Srb->Cdb, ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->Cdb, sizeof(Srb->Cdb));
|
|
foundEntry = TRUE;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
break;
|
|
|
|
case SRB_FUNCTION_WMI:
|
|
|
|
if (srbExData->Type == SrbExDataTypeWmi) {
|
|
((PSCSI_WMI_REQUEST_BLOCK)Srb)->WMISubFunction = ((PSRBEX_DATA_WMI)srbExData)->WMISubFunction;
|
|
((PSCSI_WMI_REQUEST_BLOCK)Srb)->WMIFlags = ((PSRBEX_DATA_WMI)srbExData)->WMIFlags;
|
|
((PSCSI_WMI_REQUEST_BLOCK)Srb)->DataPath = ((PSRBEX_DATA_WMI)srbExData)->DataPath;
|
|
foundEntry = TRUE;
|
|
}
|
|
break;
|
|
|
|
case SRB_FUNCTION_PNP:
|
|
|
|
if (srbExData->Type == SrbExDataTypePnP) {
|
|
((PSCSI_PNP_REQUEST_BLOCK)Srb)->PnPAction = ((PSRBEX_DATA_PNP)srbExData)->PnPAction;
|
|
((PSCSI_PNP_REQUEST_BLOCK)Srb)->PnPSubFunction = ((PSRBEX_DATA_PNP)srbExData)->PnPSubFunction;
|
|
((PSCSI_PNP_REQUEST_BLOCK)Srb)->SrbPnPFlags = ((PSRBEX_DATA_PNP)srbExData)->SrbPnPFlags;
|
|
foundEntry = TRUE;
|
|
}
|
|
break;
|
|
|
|
case SRB_FUNCTION_POWER:
|
|
|
|
if (srbExData->Type == SrbExDataTypePower) {
|
|
((PSCSI_POWER_REQUEST_BLOCK)Srb)->DevicePowerState = ((PSRBEX_DATA_POWER)srbExData)->DevicePowerState;
|
|
((PSCSI_POWER_REQUEST_BLOCK)Srb)->PowerAction = ((PSRBEX_DATA_POWER)srbExData)->PowerAction;
|
|
((PSCSI_POWER_REQUEST_BLOCK)Srb)->SrbPowerFlags = ((PSRBEX_DATA_POWER)srbExData)->SrbPowerFlags;
|
|
foundEntry = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
//
|
|
// Quit on first match
|
|
//
|
|
if (foundEntry) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
NTSTATUS
|
|
ClasspGetMaximumTokenListIdentifier(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_z_ PWSTR RegistryPath,
|
|
_Out_ PULONG MaximumListIdentifier
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the maximum ListIdentifier (to be used when building TokenOperation
|
|
requests) by querying the value MaximumListIdentifier under the key 'RegistryPath'.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - The device handling the request.
|
|
RegistryPath - The absolute registry path under which MaximumListIdentifier resides.
|
|
MaximumListIdentifier - Returns the value being queried.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS or appropriate error status returned by Registry API.
|
|
|
|
--*/
|
|
|
|
{
|
|
RTL_QUERY_REGISTRY_TABLE queryTable[2];
|
|
ULONG value = 0;
|
|
NTSTATUS status;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_PNP,
|
|
"ClasspGetMaximumTokenListIdentifier (%p): Entering function.\n",
|
|
DeviceObject));
|
|
|
|
//
|
|
// Zero the table entries.
|
|
//
|
|
RtlZeroMemory(queryTable, sizeof(queryTable));
|
|
|
|
//
|
|
// The query table has two entries. One for the MaximumListIdentifier and
|
|
// the second which is the 'NULL' terminator.
|
|
//
|
|
// Indicate that there is NO call-back routine.
|
|
//
|
|
queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK;
|
|
|
|
//
|
|
// The value to query.
|
|
//
|
|
queryTable[0].Name = REG_MAX_LIST_IDENTIFIER_VALUE;
|
|
|
|
//
|
|
// Where to put the value, the type of the value, default value and length.
|
|
//
|
|
queryTable[0].EntryContext = &value;
|
|
queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD;
|
|
queryTable[0].DefaultData = &value;
|
|
queryTable[0].DefaultLength = sizeof(value);
|
|
|
|
//
|
|
// Try to get the maximum listIdentifier.
|
|
//
|
|
status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
|
|
RegistryPath,
|
|
queryTable,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
*MaximumListIdentifier = value;
|
|
} else {
|
|
*MaximumListIdentifier = 0;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_PNP,
|
|
"ClasspGetMaximumTokenListIdentifier (%p): Exiting function with status %x (maxListId %u).\n",
|
|
DeviceObject,
|
|
status,
|
|
*MaximumListIdentifier));
|
|
|
|
return status;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
NTSTATUS
|
|
ClasspGetCopyOffloadMaxDuration(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_z_ PWSTR RegistryPath,
|
|
_Out_ PULONG MaxDuration
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the maximum time (in seconds) that a Copy Offload
|
|
operation should take to complete by a target.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - The device handling the request.
|
|
RegistryPath - The absolute registry path under which MaxDuration resides.
|
|
MaxDuration - Returns the value being queried, in seconds.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS or appropriate error status returned by Registry API.
|
|
|
|
--*/
|
|
|
|
{
|
|
RTL_QUERY_REGISTRY_TABLE queryTable[2];
|
|
ULONG value = 0;
|
|
NTSTATUS status;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_PNP,
|
|
"ClasspGetCopyOffloadMaxDuration (%p): Entering function.\n",
|
|
DeviceObject));
|
|
|
|
//
|
|
// Zero the table entries.
|
|
//
|
|
RtlZeroMemory(queryTable, sizeof(queryTable));
|
|
|
|
//
|
|
// The query table has two entries. One for CopyOffloadMaxDuration and
|
|
// the second which is the 'NULL' terminator.
|
|
//
|
|
// Indicate that there is NO call-back routine.
|
|
//
|
|
queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK;
|
|
|
|
//
|
|
// The value to query.
|
|
//
|
|
queryTable[0].Name = CLASSP_REG_COPY_OFFLOAD_MAX_TARGET_DURATION;
|
|
|
|
//
|
|
// Where to put the value, the type of the value, default value and length.
|
|
//
|
|
queryTable[0].EntryContext = &value;
|
|
queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
|
|
queryTable[0].DefaultData = &value;
|
|
queryTable[0].DefaultLength = sizeof(value);
|
|
|
|
//
|
|
// Try to get the max target duration.
|
|
//
|
|
status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
|
|
RegistryPath,
|
|
queryTable,
|
|
NULL,
|
|
NULL);
|
|
|
|
//
|
|
// Don't allow the user to set the value to lower than the default (4s) so
|
|
// they don't break ODX functionality if they accidentally set it too low.
|
|
//
|
|
if (NT_SUCCESS(status) &&
|
|
value > DEFAULT_MAX_TARGET_DURATION) {
|
|
*MaxDuration = value;
|
|
} else {
|
|
*MaxDuration = DEFAULT_MAX_TARGET_DURATION;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_PNP,
|
|
"ClasspGetCopyOffloadMaxDuration (%p): Exiting function with status %x (Max Duration %u seconds).\n",
|
|
DeviceObject,
|
|
status,
|
|
*MaxDuration));
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
_IRQL_requires_min_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
NTSTATUS
|
|
ClasspDeviceCopyOffloadProperty(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the copy offload parameters associated with the device.
|
|
|
|
This function must be called at IRQL < DISPATCH_LEVEL.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request
|
|
Irp - The IRP to be processed
|
|
Srb - The SRB associated with the request
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS status;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PSTORAGE_PROPERTY_QUERY query;
|
|
PIO_STACK_LOCATION irpStack;
|
|
ULONG length;
|
|
ULONG information;
|
|
PDEVICE_COPY_OFFLOAD_DESCRIPTOR copyOffloadDescr = (PDEVICE_COPY_OFFLOAD_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
UNREFERENCED_PARAMETER(Srb);
|
|
|
|
PAGED_CODE();
|
|
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
|
|
irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
length = 0;
|
|
information = 0;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): Entering function.\n",
|
|
DeviceObject));
|
|
|
|
//
|
|
// Check proper query type.
|
|
//
|
|
if (query->QueryType == PropertyExistsQuery) {
|
|
|
|
//
|
|
// In order to maintain consistency with the how the rest of the properties
|
|
// are handled, we shall always return success for PropertyExistsQuery.
|
|
//
|
|
status = STATUS_SUCCESS;
|
|
goto __ClasspDeviceCopyOffloadProperty_Exit;
|
|
|
|
} else if (query->QueryType != PropertyStandardQuery) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): Unsupported query type %x for Copy Offload property.\n",
|
|
DeviceObject,
|
|
query->QueryType));
|
|
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto __ClasspDeviceCopyOffloadProperty_Exit;
|
|
}
|
|
|
|
//
|
|
// Request validation.
|
|
// Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
|
|
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): Query property for Copy Offload called at incorrect IRQL.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_INVALID_LEVEL;
|
|
goto __ClasspDeviceCopyOffloadProperty_Exit;
|
|
}
|
|
|
|
length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
if (length < sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR)) {
|
|
|
|
if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
|
|
|
|
TracePrint((TRACE_LEVEL_WARNING,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): Length %u specified for Copy Offload property enough only for header.\n",
|
|
DeviceObject,
|
|
length));
|
|
|
|
information = sizeof(STORAGE_DESCRIPTOR_HEADER);
|
|
copyOffloadDescr->Version = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
|
|
copyOffloadDescr->Size = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
|
|
|
|
status = STATUS_SUCCESS;
|
|
goto __ClasspDeviceCopyOffloadProperty_Exit;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): Incorrect length %u specified for Copy Offload property.\n",
|
|
DeviceObject,
|
|
length));
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto __ClasspDeviceCopyOffloadProperty_Exit;
|
|
}
|
|
|
|
if (!fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): Command not supported on this device.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
|
|
goto __ClasspDeviceCopyOffloadProperty_Exit;
|
|
}
|
|
|
|
if (!NT_SUCCESS(fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)) {
|
|
|
|
status = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus;
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): VPD retrieval had failed with %x.\n",
|
|
DeviceObject,
|
|
status));
|
|
|
|
goto __ClasspDeviceCopyOffloadProperty_Exit;
|
|
}
|
|
|
|
//
|
|
// Fill in the output buffer. All data is copied from the FDO extension where we
|
|
// cached Block Limits and Block Device Token Limits info when the device was first initialized.
|
|
//
|
|
RtlZeroMemory(copyOffloadDescr, length);
|
|
copyOffloadDescr->Version = 1;
|
|
copyOffloadDescr->Size = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
|
|
copyOffloadDescr->MaximumTokenLifetime = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer;
|
|
copyOffloadDescr->DefaultTokenLifetime = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer;
|
|
copyOffloadDescr->MaximumTransferSize = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize;
|
|
copyOffloadDescr->OptimalTransferCount = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount;
|
|
copyOffloadDescr->MaximumDataDescriptors = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors;
|
|
|
|
if (NT_SUCCESS(fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus)) {
|
|
|
|
copyOffloadDescr->MaximumTransferLengthPerDescriptor = fdoExtension->FunctionSupportInfo->BlockLimitsData.MaximumTransferLength;
|
|
copyOffloadDescr->OptimalTransferLengthPerDescriptor = fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLength;
|
|
copyOffloadDescr->OptimalTransferLengthGranularity = fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLengthGranularity;
|
|
}
|
|
|
|
information = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
|
|
status = STATUS_SUCCESS;
|
|
|
|
__ClasspDeviceCopyOffloadProperty_Exit:
|
|
|
|
//
|
|
// Set the size and status in IRP
|
|
//
|
|
Irp->IoStatus.Information = information;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspDeviceCopyOffloadProperty (%p): Exiting function with status %x.\n",
|
|
DeviceObject,
|
|
status));
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
_IRQL_requires_min_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
NTSTATUS
|
|
ClasspValidateOffloadSupported(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine validates if this device supports offload requests.
|
|
|
|
This function must be called at IRQL < DISPATCH_LEVEL.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request
|
|
Irp - The IRP to be processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadSupported (%p): Entering function. Irp %p\n",
|
|
DeviceObject,
|
|
Irp));
|
|
|
|
fdoExt = DeviceObject->DeviceExtension;
|
|
status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// For now this command is only supported by disk devices
|
|
//
|
|
if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
|
|
(!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {
|
|
|
|
if (!fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadSupported (%p): Command not supported on this disk device.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
|
|
goto __ClasspValidateOffloadSupported_Exit;
|
|
}
|
|
|
|
if (!NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)) {
|
|
|
|
status = fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus;
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadSupported (%p): VPD retrieval failed with %x.\n",
|
|
DeviceObject,
|
|
status));
|
|
|
|
goto __ClasspValidateOffloadSupported_Exit;
|
|
}
|
|
} else {
|
|
|
|
TracePrint((TRACE_LEVEL_WARNING,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadSupported (%p): Suported only on Disk devices.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
|
|
goto __ClasspValidateOffloadSupported_Exit;
|
|
}
|
|
|
|
__ClasspValidateOffloadSupported_Exit:
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadSupported (%p): Exiting function Irp %p with status %x.\n",
|
|
DeviceObject,
|
|
Irp,
|
|
status));
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
_IRQL_requires_min_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
NTSTATUS
|
|
ClasspValidateOffloadInputParameters(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does some basic validation of the input parameters of the offload request.
|
|
|
|
This function must be called at IRQL < DISPATCH_LEVEL.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request
|
|
Irp - The IRP to be processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PIO_STACK_LOCATION irpStack;
|
|
PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
|
|
PDEVICE_DATA_SET_RANGE dataSetRanges;
|
|
ULONG dataSetRangesCount;
|
|
ULONG i;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): Entering function Irp %p.\n",
|
|
DeviceObject,
|
|
Irp));
|
|
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
irpStack = IoGetCurrentIrpStackLocation (Irp);
|
|
dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
|
|
status = STATUS_SUCCESS;
|
|
|
|
if (!dsmAttributes) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): NULL DsmAttributes passed in.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspValidateOffloadInputParameters_Exit;
|
|
}
|
|
|
|
if ((irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) ||
|
|
(irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES) + dsmAttributes->ParameterBlockLength + dsmAttributes->DataSetRangesLength))) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): Input buffer size (%u) too small.\n",
|
|
DeviceObject,
|
|
irpStack->Parameters.DeviceIoControl.InputBufferLength));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspValidateOffloadInputParameters_Exit;
|
|
}
|
|
|
|
if ((dsmAttributes->DataSetRangesOffset == 0) ||
|
|
(dsmAttributes->DataSetRangesLength == 0)) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): Incorrect DataSetRanges [offset %u, length %u].\n",
|
|
DeviceObject,
|
|
dsmAttributes->DataSetRangesOffset,
|
|
dsmAttributes->DataSetRangesLength));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspValidateOffloadInputParameters_Exit;
|
|
}
|
|
|
|
dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset);
|
|
dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
|
|
|
|
if (dataSetRangesCount == 0) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): DataSetRanges specifies no extents.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspValidateOffloadInputParameters_Exit;
|
|
}
|
|
|
|
//
|
|
// Some third party disk class drivers do not query the geometry at initialization time,
|
|
// so this information may not be available at this time. If that is the case, we'll
|
|
// first query that information before proceeding with the rest of our validations.
|
|
//
|
|
if (fdoExtension->DiskGeometry.BytesPerSector == 0) {
|
|
status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
|
|
if ((!NT_SUCCESS(status)) || (fdoExtension->DiskGeometry.BytesPerSector == 0)) {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): Couldn't retrieve disk geometry, status: %x, bytes/sector: %u.\n",
|
|
DeviceObject,
|
|
status,
|
|
fdoExtension->DiskGeometry.BytesPerSector));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspValidateOffloadInputParameters_Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Data must be aligned to sector boundary and
|
|
// LengthInBytes must be > 0 for it to be a valid LBA entry
|
|
//
|
|
for (i = 0; i < dataSetRangesCount; i++) {
|
|
if ((dataSetRanges[i].StartingOffset % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
|
|
(dataSetRanges[i].LengthInBytes % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
|
|
(dataSetRanges[i].LengthInBytes == 0) ) {
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): Incorrect DataSetRanges entry %u [offset %I64x, length %I64x].\n",
|
|
DeviceObject,
|
|
i,
|
|
dataSetRanges[i].StartingOffset,
|
|
dataSetRanges[i].LengthInBytes));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspValidateOffloadInputParameters_Exit;
|
|
}
|
|
|
|
if ((ULONGLONG)dataSetRanges[i].StartingOffset + dataSetRanges[i].LengthInBytes > (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): Error! DataSetRange %u (starting LBA %I64x) specified length %I64x exceeds the medium's capacity (%I64x).\n",
|
|
DeviceObject,
|
|
i,
|
|
dataSetRanges[i].StartingOffset,
|
|
dataSetRanges[i].LengthInBytes,
|
|
fdoExtension->CommonExtension.PartitionLength.QuadPart));
|
|
|
|
status = STATUS_NONEXISTENT_SECTOR;
|
|
goto __ClasspValidateOffloadInputParameters_Exit;
|
|
}
|
|
}
|
|
|
|
__ClasspValidateOffloadInputParameters_Exit:
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspValidateOffloadInputParameters (%p): Exiting function Irp %p with status %x.\n",
|
|
DeviceObject,
|
|
Irp,
|
|
status));
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
_IRQL_requires_same_
|
|
NTSTATUS
|
|
ClasspGetTokenOperationCommandBufferLength(
|
|
_In_ PDEVICE_OBJECT Fdo,
|
|
_In_ ULONG ServiceAction,
|
|
_Inout_ PULONG CommandBufferLength,
|
|
_Out_opt_ PULONG TokenOperationBufferLength,
|
|
_Out_opt_ PULONG ReceiveTokenInformationBufferLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine description:
|
|
|
|
This routine calculates the buffer length required to service a TokenOperation and its
|
|
corresponding ReceiveTokenInformation command.
|
|
|
|
Arguments:
|
|
|
|
Fdo - The functional device object processing the PopulateToken/WriteUsingToken request
|
|
ServiceAction - Used to distinguish between a PopulateToken and a WriteUsingToken operation
|
|
CommandBufferLength - Returns the length of the buffer needed to service the token request (i.e. TokenOperation and its corresponding ReceiveTokenInformation command)
|
|
TokenOperationBufferLength - Optional parameter, which returns the length of the buffer needed to service just the TokenOperation command.
|
|
ReceiveTokenInformationBufferLength - Optional parameter, which returns the length of the buffer needed to service just the ReceiveTokenInformation command.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
|
|
ULONG tokenOperationBufferLength;
|
|
ULONG receiveTokenInformationBufferLength;
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension;
|
|
PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExtension->PartitionZeroExtension->AdapterDescriptor;
|
|
ULONG hwMaxXferLen;
|
|
ULONG bufferLength = 0;
|
|
ULONG tokenOperationHeaderSize;
|
|
ULONG responseSize;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspGetTokenOperationCommandBufferLengths (%p): Entering function.\n",
|
|
Fdo));
|
|
|
|
NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
|
|
NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));
|
|
|
|
if (ServiceAction == SERVICE_ACTION_POPULATE_TOKEN) {
|
|
tokenOperationHeaderSize = FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor);
|
|
responseSize = FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER, TokenDescriptor) + sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR);
|
|
} else {
|
|
tokenOperationHeaderSize = FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);
|
|
responseSize = 0;
|
|
}
|
|
|
|
//
|
|
// The TokenOperation command can specify a parameter length of max 2^16.
|
|
// If the device has a max limit on the number of range descriptors that can be specified in
|
|
// the TokenOperation command, we are limited to the lesser of these two values.
|
|
//
|
|
if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors == 0) {
|
|
|
|
tokenOperationBufferLength = MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH;
|
|
|
|
} else {
|
|
|
|
tokenOperationBufferLength = MIN(tokenOperationHeaderSize + fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR),
|
|
MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH);
|
|
}
|
|
|
|
|
|
//
|
|
// The ReceiveTokenInformation command can specify a parameter length of max 2 ^ 32
|
|
// Also, since the sense data can be of variable size, we'll use MAX_SENSE_BUFFER_SIZE.
|
|
//
|
|
receiveTokenInformationBufferLength = MIN(FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE + responseSize,
|
|
MAX_RECEIVE_TOKEN_INFORMATION_PARAMETER_DATA_LENGTH);
|
|
|
|
//
|
|
// Since we're going to reuse the buffer for both the TokenOperation and the ReceiveTokenInformation
|
|
// commands, the buffer length needs to handle both operations.
|
|
//
|
|
bufferLength = MAX(tokenOperationBufferLength, receiveTokenInformationBufferLength);
|
|
|
|
//
|
|
// The buffer length needs to be further limited to the adapter's capability though.
|
|
//
|
|
hwMaxXferLen = MIN(fdoData->HwMaxXferLen, adapterDesc->MaximumTransferLength);
|
|
bufferLength = MIN(bufferLength, hwMaxXferLen);
|
|
|
|
*CommandBufferLength = bufferLength;
|
|
|
|
if (TokenOperationBufferLength) {
|
|
*TokenOperationBufferLength = tokenOperationBufferLength;
|
|
}
|
|
|
|
if (ReceiveTokenInformationBufferLength) {
|
|
*ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength;
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspGetTokenOperationCommandBufferLengths (%p): Exiting function with bufferLength %u (tokenOpBufLen %u, recTokenInfoBufLen %u).\n",
|
|
Fdo,
|
|
bufferLength,
|
|
tokenOperationBufferLength,
|
|
receiveTokenInformationBufferLength));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
_IRQL_requires_same_
|
|
NTSTATUS
|
|
ClasspGetTokenOperationDescriptorLimits(
|
|
_In_ PDEVICE_OBJECT Fdo,
|
|
_In_ ULONG ServiceAction,
|
|
_In_ ULONG MaxParameterBufferLength,
|
|
_Out_ PULONG MaxBlockDescriptorsCount,
|
|
_Out_ PULONGLONG MaxBlockDescriptorsLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine description:
|
|
|
|
This routine calculates the maximum block descriptors and the maximum token transfer size
|
|
that can be accomodated in a single TokenOperation command.
|
|
|
|
Arguments:
|
|
|
|
Fdo - The functional device object processing the PopulateToken/WriteUsingToken request
|
|
ServiceAction - Used to distinguish between a PopulateToken and a WriteUsingToken operation
|
|
MaxParameterBufferLength - The length constraint of the entire buffer for the parameter list based on other limitations (e.g. adapter max transfer length)
|
|
MaxBlockDescriptorsCount - Returns the maximum number of the block range descriptors that can be passed in a single TokenOperation command.
|
|
MaxBlockDescriptorsLength - Returns the maximum cumulative number of blocks across all the descriptors that must not be exceeded in a single TokenOperation command.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
|
|
ULONG tokenOperationHeaderSize = (ServiceAction == SERVICE_ACTION_POPULATE_TOKEN) ?
|
|
FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor) :
|
|
FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspGetTokenOperationDescriptorLimits (%p): Entering function.\n",
|
|
Fdo));
|
|
|
|
NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
|
|
NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));
|
|
|
|
*MaxBlockDescriptorsCount = (MaxParameterBufferLength - tokenOperationHeaderSize) / sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR);
|
|
*MaxBlockDescriptorsLength = (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize == 0) ?
|
|
MAX_TOKEN_TRANSFER_SIZE : fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspGetTokenOperationDescriptorLimits (%p): Exiting function with MaxDescr %u, MaxXferBlocks %I64u.\n",
|
|
Fdo,
|
|
*MaxBlockDescriptorsCount,
|
|
*MaxBlockDescriptorsLength));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
_IRQL_requires_min_(PASSIVE_LEVEL)
|
|
_IRQL_requires_same_
|
|
VOID
|
|
ClasspConvertDataSetRangeToBlockDescr(
|
|
_In_ PDEVICE_OBJECT Fdo,
|
|
_In_ PVOID BlockDescr,
|
|
_Inout_ PULONG CurrentBlockDescrIndex,
|
|
_In_ ULONG MaxBlockDescrCount,
|
|
_Inout_ PULONG CurrentLbaCount,
|
|
_In_ ULONGLONG MaxLbaCount,
|
|
_Inout_ PDEVICE_DATA_SET_RANGE DataSetRange,
|
|
_Inout_ PULONGLONG TotalSectorsProcessed
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert DEVICE_DATA_SET_RANGE entry to be WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR entries.
|
|
|
|
As LengthInBytes field in DEVICE_DATA_SET_RANGE structure is 64 bits (bytes)
|
|
and LbaCount field in WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR structure is 32 bits (sectors),
|
|
it's possible that one DEVICE_DATA_SET_RANGE entry needs multiple
|
|
WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR entries. This routine handles the need for that
|
|
potential split.
|
|
|
|
Arguments:
|
|
|
|
Fdo - The functional device object
|
|
BlockDescr - Pointer to the start of the Token Operation command's block descriptor
|
|
CurrentBlockDescrIndex - Index into the block descriptor at which to update the DataSetRange info
|
|
It also gets updated to return the index to the next empty one.
|
|
MaxBlockDescrCount - Maximum number of block descriptors that the device can handle in a single TokenOperation command
|
|
CurrentLbaCount - Returns the LBA of the last successfully processed DataSetRange
|
|
MaxLbaCount - Maximum transfer size that the device is capable of handling in a single TokenOperation command
|
|
DataSetRange - Contains information about one range extent that needs to be converted into a block descriptor
|
|
TotalSectorsProcessed - Returns the number of sectors corresponding to the DataSetRange that were succesfully mapped into block descriptors
|
|
|
|
Return Value:
|
|
|
|
Nothing.
|
|
|
|
NOTE: if LengthInBytes does not reach to 0, the conversion for DEVICE_DATA_SET_RANGE entry
|
|
is not completed. Further conversion is needed by calling this function again.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescr;
|
|
ULONGLONG startingSector;
|
|
ULONGLONG sectorCount;
|
|
ULONGLONG totalSectorCount;
|
|
ULONGLONG numberOfOptimalChunks;
|
|
USHORT optimalLbaPerDescrGranularity;
|
|
ULONG optimalLbaPerDescr;
|
|
ULONG maxLbaPerDescr;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspConvertDataSetRangeToBlockDescr (%p): Entering function. Starting offset %I64x.\n",
|
|
Fdo,
|
|
DataSetRange->StartingOffset));
|
|
|
|
fdoExtension = Fdo->DeviceExtension;
|
|
blockDescr = (PBLOCK_DEVICE_RANGE_DESCRIPTOR)BlockDescr;
|
|
totalSectorCount = 0;
|
|
|
|
|
|
//
|
|
// Since the OptimalTransferLength and the MaximumTransferLength are overloaded parameters for
|
|
// offloaded data transfers and regular read/write requests, it is not safe to use these values
|
|
// as they may report back what is used by regular read/write, which will cause a perf degradation
|
|
// in the offloaded case, since we may end up limiting the per block range descriptor length
|
|
// specified as opposed to what the target can actually handle in a single request.
|
|
// So until the SPC spec introduces these values specific to offloaded data transfers, we shall
|
|
// ignore them completely. The expectation we have from the target is as follows:
|
|
// 1. If the length specified in any of the block range descriptors is greater than the OTL that
|
|
// applies to ODX, the target will internally split into additional descriptors.
|
|
// 2. If the above causes it to run out of descriptors, or if the length specified in any of the
|
|
// descriptors is greater than the MTL that applies to ODX, the target will operate on as much
|
|
// data as possible and truncate the request to that point.
|
|
//
|
|
optimalLbaPerDescrGranularity = 0;
|
|
optimalLbaPerDescr = 0;
|
|
maxLbaPerDescr = 0;
|
|
|
|
if (optimalLbaPerDescr && maxLbaPerDescr) {
|
|
|
|
NT_ASSERT(optimalLbaPerDescr <= maxLbaPerDescr);
|
|
}
|
|
|
|
while ((DataSetRange->LengthInBytes > 0) &&
|
|
(*CurrentBlockDescrIndex < MaxBlockDescrCount) &&
|
|
(*CurrentLbaCount < MaxLbaCount)) {
|
|
|
|
startingSector = (ULONGLONG)(DataSetRange->StartingOffset / fdoExtension->DiskGeometry.BytesPerSector);
|
|
|
|
//
|
|
// Since the block descriptor has only 4 bytes for the number of logical blocks, we are
|
|
// constrained by that theoretical maximum.
|
|
//
|
|
sectorCount = MIN(DataSetRange->LengthInBytes / fdoExtension->DiskGeometry.BytesPerSector,
|
|
MAX_NUMBER_BLOCKS_PER_BLOCK_DEVICE_RANGE_DESCRIPTOR);
|
|
|
|
//
|
|
// We are constrained by MaxLbaCount.
|
|
//
|
|
if (((ULONGLONG)*CurrentLbaCount + sectorCount) >= MaxLbaCount) {
|
|
|
|
sectorCount = MaxLbaCount - *CurrentLbaCount;
|
|
}
|
|
|
|
//
|
|
// For each descriptor, the block count should be lesser than the MaximumTransferSize
|
|
//
|
|
if (maxLbaPerDescr > 0) {
|
|
|
|
//
|
|
// Each block device range descriptor can specify a max number of LBAs
|
|
//
|
|
sectorCount = MIN(sectorCount, maxLbaPerDescr);
|
|
}
|
|
|
|
//
|
|
// If the number of LBAs specified in the descriptor is greater than the OptimalTransferLength,
|
|
// processing of this descriptor by the target may incur a significant delay.
|
|
// So in order to allow the target to perform optimally, we'll further limit the number
|
|
// of blocks specified in any descriptor to be maximum OptimalTranferLength.
|
|
//
|
|
if (optimalLbaPerDescr > 0) {
|
|
|
|
sectorCount = MIN(sectorCount, optimalLbaPerDescr);
|
|
}
|
|
|
|
//
|
|
// In addition, it should either be an exact multiple of the OptimalTransferLengthGranularity,
|
|
// or be lesser than the OptimalTransferLengthGranularity (taken care of here).
|
|
//
|
|
if (optimalLbaPerDescrGranularity > 0) {
|
|
|
|
numberOfOptimalChunks = sectorCount / optimalLbaPerDescrGranularity;
|
|
|
|
if (numberOfOptimalChunks > 0) {
|
|
sectorCount = numberOfOptimalChunks * optimalLbaPerDescrGranularity;
|
|
}
|
|
}
|
|
|
|
NT_ASSERT(sectorCount <= MAX_NUMBER_BLOCKS_PER_BLOCK_DEVICE_RANGE_DESCRIPTOR);
|
|
|
|
REVERSE_BYTES_QUAD(blockDescr[*CurrentBlockDescrIndex].LogicalBlockAddress, &startingSector);
|
|
REVERSE_BYTES(blockDescr[*CurrentBlockDescrIndex].TransferLength, §orCount);
|
|
|
|
totalSectorCount += sectorCount;
|
|
|
|
DataSetRange->StartingOffset += sectorCount * fdoExtension->DiskGeometry.BytesPerSector;
|
|
DataSetRange->LengthInBytes -= sectorCount * fdoExtension->DiskGeometry.BytesPerSector;
|
|
|
|
*CurrentBlockDescrIndex += 1;
|
|
*CurrentLbaCount += (ULONG)sectorCount;
|
|
|
|
TracePrint((TRACE_LEVEL_INFORMATION,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspConvertDataSetRangeToBlockDescr (%p): Descriptor: %u, starting LBA: %I64x, length: %I64x bytes, media size: %I64x.\n",
|
|
Fdo,
|
|
*CurrentBlockDescrIndex - 1,
|
|
startingSector,
|
|
sectorCount * fdoExtension->DiskGeometry.BytesPerSector,
|
|
(ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart));
|
|
}
|
|
|
|
*TotalSectorsProcessed = totalSectorCount;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspConvertDataSetRangeToBlockDescr (%p): Exiting function (starting offset %I64x). Total sectors processed %I64u.\n",
|
|
Fdo,
|
|
DataSetRange->StartingOffset,
|
|
totalSectorCount));
|
|
|
|
return;
|
|
}
|
|
|
|
_IRQL_requires_same_
|
|
PUCHAR
|
|
ClasspBinaryToAscii(
|
|
_In_reads_(Length) PUCHAR HexBuffer,
|
|
_In_ ULONG Length,
|
|
_Inout_ PULONG UpdateLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will convert HexBuffer into an ascii NULL-terminated string.
|
|
|
|
Note: This routine will allocate memory for storing the ascii string. It is
|
|
the responsibility of the caller to free this buffer.
|
|
|
|
Arguments:
|
|
|
|
HexBuffer - Pointer to the binary data.
|
|
Length - Length, in bytes, of HexBuffer.
|
|
UpdateLength - Storage to place the actual length of the returned string.
|
|
|
|
Return Value:
|
|
|
|
ASCII string equivalent of the hex buffer, or NULL if an error occurred.
|
|
|
|
--*/
|
|
|
|
{
|
|
static const UCHAR integerTable[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
|
|
ULONG i;
|
|
ULONG j;
|
|
ULONG actualLength;
|
|
PUCHAR buffer = NULL;
|
|
UCHAR highWord;
|
|
UCHAR lowWord;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspBinaryToAscii (HexBuff %p): Entering function.\n",
|
|
HexBuffer));
|
|
|
|
if (!HexBuffer || Length == 0) {
|
|
*UpdateLength = 0;
|
|
goto __ClasspBinaryToAscii_Exit;
|
|
}
|
|
|
|
//
|
|
// Each byte converts into 2 chars:
|
|
// e.g. 0x05 => '0' '5'
|
|
// 0x0C => '0' 'C'
|
|
// 0x12 => '1' '2'
|
|
// And we need a terminating NULL for the string.
|
|
//
|
|
actualLength = (Length * 2) + 1;
|
|
|
|
//
|
|
// Allocate the buffer.
|
|
//
|
|
buffer = ExAllocatePoolWithTag(NonPagedPoolNx, actualLength, CLASSPNP_POOL_TAG_TOKEN_OPERATION);
|
|
if (!buffer) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspBinaryToAscii (HexBuff %p): Failed to allocate buffer for ASCII equivalent.\n",
|
|
HexBuffer));
|
|
|
|
*UpdateLength = 0;
|
|
goto __ClasspBinaryToAscii_Exit;
|
|
}
|
|
|
|
RtlZeroMemory(buffer, actualLength);
|
|
|
|
for (i = 0, j = 0; i < Length; i++) {
|
|
|
|
//
|
|
// Split out each nibble from the binary byte.
|
|
//
|
|
highWord = HexBuffer[i] >> 4;
|
|
lowWord = HexBuffer[i] & 0x0F;
|
|
|
|
//
|
|
// Using the lookup table, convert and stuff into
|
|
// the ascii buffer.
|
|
//
|
|
buffer[j++] = integerTable[highWord];
|
|
#ifdef _MSC_VER
|
|
#pragma warning(suppress: 6386) // PREFast bug means it doesn't see that Length < actualLength
|
|
#endif
|
|
buffer[j++] = integerTable[lowWord];
|
|
}
|
|
|
|
//
|
|
// Update the caller's length field.
|
|
//
|
|
*UpdateLength = actualLength;
|
|
|
|
__ClasspBinaryToAscii_Exit:
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspBinaryToAscii (HexBuff %p): Exiting function with buffer %s.\n",
|
|
HexBuffer,
|
|
(buffer == NULL) ? "" : (const char*)buffer));
|
|
|
|
return buffer;
|
|
}
|
|
|
|
_IRQL_requires_same_
|
|
NTSTATUS
|
|
ClasspStorageEventNotification(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine handles an asynchronous event notification (most likely from
|
|
port drivers). Currently, we only care about media status change events.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request
|
|
Irp - The IRP to be processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PIO_STACK_LOCATION irpStack;
|
|
PSTORAGE_EVENT_NOTIFICATION storageEvents;
|
|
NTSTATUS status;
|
|
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspStorageEventNotification (%p): Entering function Irp %p.\n",
|
|
DeviceObject,
|
|
Irp));
|
|
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
irpStack = IoGetCurrentIrpStackLocation (Irp);
|
|
storageEvents = Irp->AssociatedIrp.SystemBuffer;
|
|
status = STATUS_SUCCESS;
|
|
|
|
if (!storageEvents) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspStorageEventNotification (%p): NULL storage events passed in.\n",
|
|
DeviceObject));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspStorageEventNotification_Exit;
|
|
}
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_EVENT_NOTIFICATION)) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspStorageEventNotification (%p): Input buffer size (%u) too small.\n",
|
|
DeviceObject,
|
|
irpStack->Parameters.DeviceIoControl.InputBufferLength));
|
|
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto __ClasspStorageEventNotification_Exit;
|
|
}
|
|
|
|
if ((storageEvents->Version != STORAGE_EVENT_NOTIFICATION_VERSION_V1) ||
|
|
(storageEvents->Size != sizeof(STORAGE_EVENT_NOTIFICATION))) {
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspStorageEventNotification (%p): Invalid version/size [version %u, size %u].\n",
|
|
DeviceObject,
|
|
storageEvents->Version,
|
|
storageEvents->Size));
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto __ClasspStorageEventNotification_Exit;
|
|
}
|
|
|
|
//
|
|
// Handle a media status event.
|
|
//
|
|
if (storageEvents->Events & STORAGE_EVENT_MEDIA_STATUS) {
|
|
|
|
//
|
|
// Only initiate operation if underlying port driver supports asynchronous notification
|
|
// and this is the FDO.
|
|
//
|
|
if ((fdoExtension->CommonExtension.IsFdo == TRUE) &&
|
|
(fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported)) {
|
|
ClassCheckMediaState(fdoExtension);
|
|
} else {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
}
|
|
|
|
__ClasspStorageEventNotification_Exit:
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_IOCTL,
|
|
"ClasspStorageEventNotification (%p): Exiting function Irp %p with status %x.\n",
|
|
DeviceObject,
|
|
Irp,
|
|
status));
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
Irp->IoStatus.Status = status;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
VOID
|
|
ClasspZeroQERR(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will attempt to set the QERR bit of the mode Control page to
|
|
zero.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PMODE_PARAMETER_HEADER modeData = NULL;
|
|
PMODE_CONTROL_PAGE pageData = NULL;
|
|
ULONG size = 0;
|
|
|
|
modeData = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
|
|
MODE_PAGE_DATA_SIZE,
|
|
CLASS_TAG_MODE_DATA);
|
|
|
|
if (modeData == NULL) {
|
|
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_SCSI, "ClasspZeroQERR: Unable to allocate mode data buffer\n"));
|
|
goto ClasspZeroQERR_Exit;
|
|
}
|
|
|
|
RtlZeroMemory(modeData, MODE_PAGE_DATA_SIZE);
|
|
|
|
size = ClassModeSense(DeviceObject,
|
|
(PCHAR) modeData,
|
|
MODE_PAGE_DATA_SIZE,
|
|
MODE_PAGE_CONTROL);
|
|
|
|
if (size < sizeof(MODE_PARAMETER_HEADER)) {
|
|
|
|
//
|
|
// Retry the request in case of a check condition.
|
|
//
|
|
|
|
size = ClassModeSense(DeviceObject,
|
|
(PCHAR) modeData,
|
|
MODE_PAGE_DATA_SIZE,
|
|
MODE_PAGE_CONTROL);
|
|
|
|
if (size < sizeof(MODE_PARAMETER_HEADER)) {
|
|
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_SCSI, "ClasspZeroQERR: Mode Sense failed\n"));
|
|
goto ClasspZeroQERR_Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the size is greater than size indicated by the mode data reset
|
|
// the data to the mode data.
|
|
//
|
|
|
|
if (size > (ULONG) (modeData->ModeDataLength + 1)) {
|
|
size = modeData->ModeDataLength + 1;
|
|
}
|
|
|
|
//
|
|
// Look for control page in the returned mode page data.
|
|
//
|
|
|
|
pageData = ClassFindModePage((PCHAR) modeData,
|
|
size,
|
|
MODE_PAGE_CONTROL,
|
|
TRUE);
|
|
|
|
if (pageData) {
|
|
TracePrint((TRACE_LEVEL_VERBOSE,
|
|
TRACE_FLAG_SCSI,
|
|
"ClasspZeroQERR (%p): Current settings: QERR = %u, TST = %u, TAS = %u.\n",
|
|
DeviceObject,
|
|
pageData->QERR,
|
|
pageData->TST,
|
|
pageData->TAS));
|
|
|
|
if (pageData->QERR != 0) {
|
|
NTSTATUS status;
|
|
UCHAR pageSavable = 0;
|
|
|
|
//
|
|
// Set QERR to 0 with a Mode Select command. Re-use the modeData
|
|
// and pageData structures.
|
|
//
|
|
pageData->QERR = 0;
|
|
|
|
//
|
|
// We use the original Page Savable (PS) value for the Save Pages
|
|
// (SP) bit due to behavior described under the MODE SELECT(6)
|
|
// section of SPC-4.
|
|
//
|
|
pageSavable = pageData->PageSavable;
|
|
|
|
status = ClasspModeSelect(DeviceObject,
|
|
(PCHAR)modeData,
|
|
size,
|
|
pageSavable);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
TracePrint((TRACE_LEVEL_WARNING,
|
|
TRACE_FLAG_SCSI,
|
|
"ClasspZeroQERR (%p): Failed to set QERR = 0 with status %x\n",
|
|
DeviceObject,
|
|
status));
|
|
}
|
|
}
|
|
}
|
|
|
|
ClasspZeroQERR_Exit:
|
|
|
|
if (modeData != NULL) {
|
|
ExFreePool(modeData);
|
|
}
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
NTSTATUS
|
|
ClasspPowerActivateDevice(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine synchronously sends an IOCTL_STORAGE_POWER_ACTIVE to the port
|
|
PDO in order to take an active reference on the given device. The device
|
|
will remain powered up and active for as long as this active reference is
|
|
taken.
|
|
|
|
The caller should ensure idle power management is enabled for the device
|
|
before calling this function.
|
|
|
|
Call ClasspPowerIdleDevice to release the active reference.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the FDO associated with this request.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if the active reference was successfully taken.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PIRP irp;
|
|
KEVENT event;
|
|
IO_STATUS_BLOCK ioStatus;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
|
|
NT_ASSERT(fdoExtension->CommonExtension.IsFdo);
|
|
NT_ASSERT(fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled);
|
|
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
|
|
irp = IoBuildDeviceIoControlRequest(IOCTL_STORAGE_POWER_ACTIVE,
|
|
fdoExtension->LowerPdo,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0,
|
|
FALSE,
|
|
&event,
|
|
&ioStatus);
|
|
|
|
if (irp != NULL) {
|
|
status = IoCallDriver(fdoExtension->LowerPdo, irp);
|
|
if (status == STATUS_PENDING) {
|
|
(VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
status = ioStatus.Status;
|
|
}
|
|
} else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
NTSTATUS
|
|
ClasspPowerIdleDevice(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine synchronously sends an IOCTL_STORAGE_POWER_IDLE to the port
|
|
PDO in order to release an active reference on the given device.
|
|
|
|
A call to ClasspPowerActivateDevice *must* have preceded a call to this
|
|
function.
|
|
|
|
The caller should ensure idle power management is enabled for the device
|
|
before calling this function.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the FDO associated with this request.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if the active reference was successfully released.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PIRP irp;
|
|
KEVENT event;
|
|
IO_STATUS_BLOCK ioStatus;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
|
|
NT_ASSERT(fdoExtension->CommonExtension.IsFdo);
|
|
NT_ASSERT(fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled);
|
|
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
|
|
irp = IoBuildDeviceIoControlRequest(IOCTL_STORAGE_POWER_IDLE,
|
|
fdoExtension->LowerPdo,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0,
|
|
FALSE,
|
|
&event,
|
|
&ioStatus);
|
|
|
|
if (irp != NULL) {
|
|
status = IoCallDriver(fdoExtension->LowerPdo, irp);
|
|
if (status == STATUS_PENDING) {
|
|
(VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
status = ioStatus.Status;
|
|
}
|
|
} else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
NTSTATUS
|
|
ClasspGetHwFirmwareInfo(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
PSTORAGE_HW_FIRMWARE_INFO firmwareInfo = NULL;
|
|
PSTORAGE_HW_FIRMWARE_INFO_QUERY query = NULL;
|
|
|
|
IO_STATUS_BLOCK ioStatus = {0};
|
|
ULONG dataLength = sizeof(STORAGE_HW_FIRMWARE_INFO);
|
|
ULONG iteration = 1;
|
|
|
|
CLASS_FUNCTION_SUPPORT oldState;
|
|
KLOCK_QUEUE_HANDLE lockHandle;
|
|
|
|
//
|
|
// Try to get firmware information that contains only one slot.
|
|
// We will retry the query if the required buffer size is bigger than that.
|
|
//
|
|
retry:
|
|
|
|
firmwareInfo = ExAllocatePoolWithTag(NonPagedPoolNx, dataLength, CLASSPNP_POOL_TAG_FIRMWARE);
|
|
|
|
if (firmwareInfo == NULL) {
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClasspGetHwFirmwareInfo: cannot allocate memory to hold data. \n"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(firmwareInfo, dataLength);
|
|
|
|
//
|
|
// Set up query data, making sure the "Flags" field indicating the request is for device itself.
|
|
//
|
|
query = (PSTORAGE_HW_FIRMWARE_INFO_QUERY)firmwareInfo;
|
|
|
|
query->Version = sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY);
|
|
query->Size = sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY);
|
|
query->Flags = 0;
|
|
|
|
//
|
|
// On the first pass we just want to get the first few
|
|
// bytes of the descriptor so we can read it's size
|
|
//
|
|
ClassSendDeviceIoControlSynchronous(IOCTL_STORAGE_FIRMWARE_GET_INFO,
|
|
commonExtension->LowerDeviceObject,
|
|
query,
|
|
sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY),
|
|
dataLength,
|
|
FALSE,
|
|
&ioStatus
|
|
);
|
|
|
|
if (!NT_SUCCESS(ioStatus.Status) &&
|
|
(ioStatus.Status != STATUS_BUFFER_OVERFLOW)) {
|
|
if (ClasspLowerLayerNotSupport(ioStatus.Status)) {
|
|
oldState = InterlockedCompareExchange((PLONG)(&fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport), (LONG)NotSupported, (ULONG)SupportUnknown);
|
|
}
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClasspGetHwFirmwareInfo: error %lx trying to "
|
|
"query hardware firmware information #%d \n", ioStatus.Status, iteration));
|
|
FREE_POOL(firmwareInfo);
|
|
return ioStatus.Status;
|
|
}
|
|
|
|
//
|
|
// Catch implementation issues from lower level driver.
|
|
//
|
|
if ((firmwareInfo->Version < sizeof(STORAGE_HW_FIRMWARE_INFO)) ||
|
|
(firmwareInfo->Size < sizeof(STORAGE_HW_FIRMWARE_INFO)) ||
|
|
(firmwareInfo->SlotCount == 0) ||
|
|
(firmwareInfo->ImagePayloadMaxSize > fdoExtension->AdapterDescriptor->MaximumTransferLength)) {
|
|
|
|
oldState = InterlockedCompareExchange((PLONG)(&fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport), (LONG)NotSupported, (ULONG)SupportUnknown);
|
|
|
|
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClasspGetHwFirmwareInfo: error in returned data! "
|
|
"Version: 0x%X, Size: 0x%X, SlotCount: 0x%X, ActiveSlot: 0x%X, PendingActiveSlot: 0x%X, ImagePayloadMaxSize: 0x%X \n",
|
|
firmwareInfo->Version,
|
|
firmwareInfo->Size,
|
|
firmwareInfo->SlotCount,
|
|
firmwareInfo->ActiveSlot,
|
|
firmwareInfo->PendingActivateSlot,
|
|
firmwareInfo->ImagePayloadMaxSize));
|
|
|
|
FREE_POOL(firmwareInfo);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
//
|
|
// If the data size is bigger than sizeof(STORAGE_HW_FIRMWARE_INFO), e.g. device has more than one firmware slot,
|
|
// allocate a buffer to get all the data.
|
|
//
|
|
if ((firmwareInfo->Size > sizeof(STORAGE_HW_FIRMWARE_INFO)) &&
|
|
(iteration < 2)) {
|
|
|
|
dataLength = max(firmwareInfo->Size, sizeof(STORAGE_HW_FIRMWARE_INFO) + sizeof(STORAGE_HW_FIRMWARE_SLOT_INFO) * (firmwareInfo->SlotCount - 1));
|
|
|
|
//
|
|
// Retry the query with required buffer length.
|
|
//
|
|
FREE_POOL(firmwareInfo);
|
|
iteration++;
|
|
goto retry;
|
|
}
|
|
|
|
|
|
//
|
|
// Set the support status and use the memory we've allocated as caching buffer.
|
|
// In case of a competing thread already set the state, it will assign the caching buffer so release the current allocated one.
|
|
//
|
|
KeAcquireInStackQueuedSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
|
|
|
|
oldState = InterlockedCompareExchange((PLONG)(&fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport), (LONG)Supported, (ULONG)SupportUnknown);
|
|
|
|
if (oldState == SupportUnknown) {
|
|
fdoExtension->FunctionSupportInfo->HwFirmwareInfo = firmwareInfo;
|
|
} else if (oldState == Supported) {
|
|
//
|
|
// swap the buffers to keep the latest version.
|
|
//
|
|
PSTORAGE_HW_FIRMWARE_INFO cachedInfo = fdoExtension->FunctionSupportInfo->HwFirmwareInfo;
|
|
|
|
fdoExtension->FunctionSupportInfo->HwFirmwareInfo = firmwareInfo;
|
|
|
|
FREE_POOL(cachedInfo);
|
|
} else {
|
|
FREE_POOL(firmwareInfo);
|
|
}
|
|
|
|
KeReleaseInStackQueuedSpinLock(&lockHandle);
|
|
|
|
return ioStatus.Status;
|
|
} // end ClasspGetHwFirmwareInfo()
|
|
|
|
#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
#ifndef __REACTOS__ // the functions is not used
|
|
__inline
|
|
BOOLEAN
|
|
ClassDeviceHwFirmwareIsPortDriverSupported(
|
|
_In_ PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function informs the caller whether the port driver supports hardware firmware requests.
|
|
|
|
Arguments:
|
|
DeviceObject: The target object.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the port driver is supported.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// If the request is for a FDO, process the request for Storport, SDstor and Spaceport only.
|
|
// Don't process it if we don't have a miniport descriptor.
|
|
//
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
BOOLEAN isSupported = FALSE;
|
|
if (commonExtension->IsFdo && (fdoExtension->MiniportDescriptor != NULL)) {
|
|
isSupported = ((fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetStorport) ||
|
|
(fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetSpaceport) ||
|
|
(fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetSDport ));
|
|
}
|
|
|
|
return isSupported;
|
|
}
|
|
#endif
|
|
|
|
NTSTATUS
|
|
ClassDeviceHwFirmwareGetInfoProcess(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp
|
|
)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function processes the Storage Hardware Firmware Get Information request.
|
|
If the information is not cached yet, it gets from lower level driver.
|
|
|
|
Arguments:
|
|
DeviceObject: The target FDO.
|
|
Irp: The IRP which will contain the output buffer upon completion.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSTORAGE_HW_FIRMWARE_INFO_QUERY query = (PSTORAGE_HW_FIRMWARE_INFO_QUERY)Irp->AssociatedIrp.SystemBuffer;
|
|
BOOLEAN passDown = FALSE;
|
|
BOOLEAN copyData = FALSE;
|
|
|
|
|
|
//
|
|
// Input buffer is not big enough to contain required input information.
|
|
//
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY)) {
|
|
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto Exit_Firmware_Get_Info;
|
|
}
|
|
|
|
//
|
|
// Output buffer is too small to contain return data.
|
|
//
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_HW_FIRMWARE_INFO)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto Exit_Firmware_Get_Info;
|
|
}
|
|
|
|
//
|
|
// Only process the request for a supported port driver.
|
|
//
|
|
if (!ClassDeviceHwFirmwareIsPortDriverSupported(DeviceObject)) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
goto Exit_Firmware_Get_Info;
|
|
}
|
|
|
|
//
|
|
// Buffer "FunctionSupportInfo" is allocated during start device process. Following check defends against the situation
|
|
// of receiving this IOCTL when the device is created but not started, or device start failed but did not get removed yet.
|
|
//
|
|
if (commonExtension->IsFdo && (fdoExtension->FunctionSupportInfo == NULL)) {
|
|
|
|
status = STATUS_UNSUCCESSFUL;
|
|
goto Exit_Firmware_Get_Info;
|
|
}
|
|
|
|
//
|
|
// Process the situation that request should be forwarded to lower level.
|
|
//
|
|
if (!commonExtension->IsFdo) {
|
|
passDown = TRUE;
|
|
}
|
|
|
|
if ((query->Flags & STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER) != 0) {
|
|
passDown = TRUE;
|
|
}
|
|
|
|
if (passDown) {
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// The request is for a FDO. Process the request.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
goto Exit_Firmware_Get_Info;
|
|
} else {
|
|
//
|
|
// Retrieve information from lower layer for the request. The cached information is not used
|
|
// in case device firmware information changed.
|
|
//
|
|
status = ClasspGetHwFirmwareInfo(DeviceObject);
|
|
copyData = NT_SUCCESS(status);
|
|
}
|
|
|
|
Exit_Firmware_Get_Info:
|
|
|
|
if (copyData) {
|
|
//
|
|
// Firmware information is already cached in classpnp. Return a copy.
|
|
//
|
|
KLOCK_QUEUE_HANDLE lockHandle;
|
|
KeAcquireInStackQueuedSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
|
|
|
|
ULONG dataLength = min(irpStack->Parameters.DeviceIoControl.OutputBufferLength, fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Size);
|
|
|
|
memcpy(Irp->AssociatedIrp.SystemBuffer, fdoExtension->FunctionSupportInfo->HwFirmwareInfo, dataLength);
|
|
|
|
KeReleaseInStackQueuedSpinLock(&lockHandle);
|
|
|
|
Irp->IoStatus.Information = dataLength;
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
#else
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
Irp->IoStatus.Status = status;
|
|
#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
_IRQL_requires_same_
|
|
_IRQL_requires_max_(DISPATCH_LEVEL)
|
|
NTSTATUS
|
|
ClassHwFirmwareDownloadComplete (
|
|
_In_ PDEVICE_OBJECT Fdo,
|
|
_In_ PIRP Irp,
|
|
_In_reads_opt_(_Inexpressible_("varies")) PVOID Context
|
|
)
|
|
{
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
PIRP originalIrp;
|
|
|
|
//
|
|
// Free the allocated buffer for firmware image.
|
|
//
|
|
if (Context != NULL) {
|
|
FREE_POOL(Context);
|
|
}
|
|
|
|
originalIrp = irpStack->Parameters.Others.Argument1;
|
|
|
|
NT_ASSERT(originalIrp != NULL);
|
|
|
|
originalIrp->IoStatus.Status = Irp->IoStatus.Status;
|
|
originalIrp->IoStatus.Information = Irp->IoStatus.Information;
|
|
|
|
ClassReleaseRemoveLock(Fdo, originalIrp);
|
|
ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT);
|
|
|
|
IoFreeIrp(Irp);
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
} // end ClassHwFirmwareDownloadComplete()
|
|
#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
|
|
NTSTATUS
|
|
ClassDeviceHwFirmwareDownloadProcess(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSTORAGE_HW_FIRMWARE_DOWNLOAD firmwareDownload = (PSTORAGE_HW_FIRMWARE_DOWNLOAD)Irp->AssociatedIrp.SystemBuffer;
|
|
BOOLEAN passDown = FALSE;
|
|
ULONG i;
|
|
ULONG bufferSize = 0;
|
|
PUCHAR firmwareImageBuffer = NULL;
|
|
PIRP irp2 = NULL;
|
|
PIO_STACK_LOCATION newStack = NULL;
|
|
PCDB cdb = NULL;
|
|
BOOLEAN lockHeld = FALSE;
|
|
KLOCK_QUEUE_HANDLE lockHandle;
|
|
|
|
|
|
//
|
|
// Input buffer is not big enough to contain required input information.
|
|
//
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_HW_FIRMWARE_DOWNLOAD)) {
|
|
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Input buffer basic validation.
|
|
//
|
|
if ((firmwareDownload->Version < sizeof(STORAGE_HW_FIRMWARE_DOWNLOAD)) ||
|
|
(firmwareDownload->Size > irpStack->Parameters.DeviceIoControl.InputBufferLength) ||
|
|
((firmwareDownload->BufferSize + FIELD_OFFSET(STORAGE_HW_FIRMWARE_DOWNLOAD, ImageBuffer)) > firmwareDownload->Size)) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Only process the request for a supported port driver.
|
|
//
|
|
if (!ClassDeviceHwFirmwareIsPortDriverSupported(DeviceObject)) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Buffer "FunctionSupportInfo" is allocated during start device process. Following check defends against the situation
|
|
// of receiving this IOCTL when the device is created but not started, or device start failed but did not get removed yet.
|
|
//
|
|
if (commonExtension->IsFdo && (fdoExtension->FunctionSupportInfo == NULL)) {
|
|
|
|
status = STATUS_UNSUCCESSFUL;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Process the situation that request should be forwarded to lower level.
|
|
//
|
|
if (!commonExtension->IsFdo) {
|
|
passDown = TRUE;
|
|
}
|
|
|
|
if ((firmwareDownload->Flags & STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER) != 0) {
|
|
passDown = TRUE;
|
|
}
|
|
|
|
if (passDown) {
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
FREE_POOL(Srb);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// If firmware information hasn't been cached in classpnp, retrieve it.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
goto Exit_Firmware_Download;
|
|
} else {
|
|
//
|
|
// If this is the first time of retrieving firmware information,
|
|
// send request to lower level to get it.
|
|
//
|
|
status = ClasspGetHwFirmwareInfo(DeviceObject);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fail the request if the firmware information cannot be retrieved.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
} else {
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Acquire the SyncLock to ensure the HwFirmwareInfo pointer doesn't change
|
|
// while we're dereferencing it.
|
|
//
|
|
lockHeld = TRUE;
|
|
KeAcquireInStackQueuedSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
|
|
|
|
//
|
|
// Validate the device support
|
|
//
|
|
if ((fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SupportUpgrade == FALSE) ||
|
|
(fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment == 0)) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Check if the slot can be used to hold firmware image.
|
|
//
|
|
for (i = 0; i < fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount; i++) {
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Slot[i].SlotNumber == firmwareDownload->Slot) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((i >= fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount) ||
|
|
(fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Slot[i].ReadOnly == TRUE)) {
|
|
//
|
|
// Either the slot number is out of scope or the slot is read-only.
|
|
//
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Buffer size and alignment validation.
|
|
// Max Offset and Buffer Size can be represented by SCSI command is max value for 3 bytes.
|
|
//
|
|
if ((firmwareDownload->BufferSize == 0) ||
|
|
((firmwareDownload->BufferSize % fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment) != 0) ||
|
|
(firmwareDownload->BufferSize > fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadMaxSize) ||
|
|
(firmwareDownload->BufferSize > fdoExtension->AdapterDescriptor->MaximumTransferLength) ||
|
|
((firmwareDownload->Offset % fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment) != 0) ||
|
|
(firmwareDownload->Offset > 0xFFFFFF) ||
|
|
(firmwareDownload->BufferSize > 0xFFFFFF)) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
|
|
//
|
|
// Process the request by translating it into WRITE BUFFER command.
|
|
//
|
|
if (((ULONG_PTR)firmwareDownload->ImageBuffer % fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment) != 0) {
|
|
//
|
|
// Allocate buffer aligns to ImagePayloadAlignment to accommodate the alignment requirement.
|
|
//
|
|
bufferSize = ALIGN_UP_BY(firmwareDownload->BufferSize, fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment);
|
|
|
|
//
|
|
// We're done accessing HwFirmwareInfo at this point so we can release
|
|
// the SyncLock.
|
|
//
|
|
NT_ASSERT(lockHeld);
|
|
KeReleaseInStackQueuedSpinLock(&lockHandle);
|
|
lockHeld = FALSE;
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma prefast(suppress:6014, "The allocated memory that firmwareImageBuffer points to will be freed in ClassHwFirmwareDownloadComplete().")
|
|
#endif
|
|
firmwareImageBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferSize, CLASSPNP_POOL_TAG_FIRMWARE);
|
|
|
|
if (firmwareImageBuffer == NULL) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
RtlZeroMemory(firmwareImageBuffer, bufferSize);
|
|
|
|
RtlCopyMemory(firmwareImageBuffer, firmwareDownload->ImageBuffer, (ULONG)firmwareDownload->BufferSize);
|
|
|
|
} else {
|
|
NT_ASSERT(lockHeld);
|
|
KeReleaseInStackQueuedSpinLock(&lockHandle);
|
|
lockHeld = FALSE;
|
|
|
|
firmwareImageBuffer = firmwareDownload->ImageBuffer;
|
|
bufferSize = (ULONG)firmwareDownload->BufferSize;
|
|
}
|
|
|
|
//
|
|
// Allocate a new irp to send the WRITE BUFFER command down.
|
|
// Similar process as IOCTL_STORAGE_CHECK_VERIFY.
|
|
//
|
|
irp2 = IoAllocateIrp((CCHAR)(DeviceObject->StackSize + 3), FALSE);
|
|
|
|
if (irp2 == NULL) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
if (firmwareImageBuffer != firmwareDownload->ImageBuffer) {
|
|
FREE_POOL(firmwareImageBuffer);
|
|
}
|
|
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
//
|
|
// Make sure to acquire the lock for the new irp.
|
|
//
|
|
ClassAcquireRemoveLock(DeviceObject, irp2);
|
|
|
|
irp2->Tail.Overlay.Thread = Irp->Tail.Overlay.Thread;
|
|
IoSetNextIrpStackLocation(irp2);
|
|
|
|
//
|
|
// Set the top stack location and shove the master Irp into the
|
|
// top location
|
|
//
|
|
newStack = IoGetCurrentIrpStackLocation(irp2);
|
|
newStack->Parameters.Others.Argument1 = Irp;
|
|
newStack->DeviceObject = DeviceObject;
|
|
|
|
//
|
|
// Stick the firmware download completion routine onto the stack
|
|
// and prepare the irp for the port driver
|
|
//
|
|
IoSetCompletionRoutine(irp2,
|
|
ClassHwFirmwareDownloadComplete,
|
|
(firmwareImageBuffer != firmwareDownload->ImageBuffer) ? firmwareImageBuffer : NULL,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
|
|
IoSetNextIrpStackLocation(irp2);
|
|
newStack = IoGetCurrentIrpStackLocation(irp2);
|
|
newStack->DeviceObject = DeviceObject;
|
|
newStack->MajorFunction = irpStack->MajorFunction;
|
|
newStack->MinorFunction = irpStack->MinorFunction;
|
|
newStack->Flags = irpStack->Flags;
|
|
|
|
|
|
//
|
|
// Mark the master irp as pending - whether the lower level
|
|
// driver completes it immediately or not this should allow it
|
|
// to go all the way back up.
|
|
//
|
|
IoMarkIrpPending(Irp);
|
|
|
|
//
|
|
// Setup the CDB.
|
|
//
|
|
SrbSetCdbLength(Srb, CDB10GENERIC_LENGTH);
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->WRITE_BUFFER.OperationCode = SCSIOP_WRITE_DATA_BUFF;
|
|
cdb->WRITE_BUFFER.Mode = SCSI_WRITE_BUFFER_MODE_DOWNLOAD_MICROCODE_WITH_OFFSETS_SAVE_DEFER_ACTIVATE;
|
|
cdb->WRITE_BUFFER.ModeSpecific = 0; //Reserved for Mode 0x0E
|
|
cdb->WRITE_BUFFER.BufferID = firmwareDownload->Slot;
|
|
|
|
cdb->WRITE_BUFFER.BufferOffset[0] = *((PCHAR)&firmwareDownload->Offset + 2);
|
|
cdb->WRITE_BUFFER.BufferOffset[1] = *((PCHAR)&firmwareDownload->Offset + 1);
|
|
cdb->WRITE_BUFFER.BufferOffset[2] = *((PCHAR)&firmwareDownload->Offset);
|
|
|
|
cdb->WRITE_BUFFER.ParameterListLength[0] = *((PCHAR)&bufferSize + 2);
|
|
cdb->WRITE_BUFFER.ParameterListLength[1] = *((PCHAR)&bufferSize + 1);
|
|
cdb->WRITE_BUFFER.ParameterListLength[2] = *((PCHAR)&bufferSize);
|
|
|
|
//
|
|
// Send as a tagged command.
|
|
//
|
|
SrbSetRequestAttribute(Srb, SRB_HEAD_OF_QUEUE_TAG_REQUEST);
|
|
SrbSetSrbFlags(Srb, SRB_FLAGS_NO_QUEUE_FREEZE | SRB_FLAGS_QUEUE_ACTION_ENABLE);
|
|
|
|
//
|
|
// Set timeout value.
|
|
//
|
|
SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
|
|
|
|
//
|
|
// This routine uses a completion routine so we don't want to release
|
|
// the remove lock until then.
|
|
//
|
|
status = ClassSendSrbAsynchronous(DeviceObject,
|
|
Srb,
|
|
irp2,
|
|
firmwareImageBuffer,
|
|
bufferSize,
|
|
TRUE);
|
|
|
|
if (status != STATUS_PENDING) {
|
|
//
|
|
// If the new irp cannot be sent down, free allocated memory and bail out.
|
|
//
|
|
if (firmwareImageBuffer != firmwareDownload->ImageBuffer) {
|
|
FREE_POOL(firmwareImageBuffer);
|
|
}
|
|
|
|
//
|
|
// If the irp cannot be sent down, the Srb has been freed. NULL it to prevent from freeing it again.
|
|
//
|
|
Srb = NULL;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, irp2);
|
|
|
|
IoFreeIrp(irp2);
|
|
|
|
goto Exit_Firmware_Download;
|
|
}
|
|
|
|
return status;
|
|
|
|
Exit_Firmware_Download:
|
|
|
|
//
|
|
// Release the SyncLock if it's still held.
|
|
// This should only happen in the failure path.
|
|
//
|
|
if (lockHeld) {
|
|
KeReleaseInStackQueuedSpinLock(&lockHandle);
|
|
lockHeld = FALSE;
|
|
}
|
|
|
|
//
|
|
// Firmware Download request will be failed.
|
|
//
|
|
NT_ASSERT(!NT_SUCCESS(status));
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
#else
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
Irp->IoStatus.Status = status;
|
|
#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
FREE_POOL(Srb);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
ClassDeviceHwFirmwareActivateProcess(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_Inout_ PIRP Irp,
|
|
_Inout_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSTORAGE_HW_FIRMWARE_ACTIVATE firmwareActivate = (PSTORAGE_HW_FIRMWARE_ACTIVATE)Irp->AssociatedIrp.SystemBuffer;
|
|
BOOLEAN passDown = FALSE;
|
|
PCDB cdb = NULL;
|
|
ULONG i;
|
|
BOOLEAN lockHeld = FALSE;
|
|
KLOCK_QUEUE_HANDLE lockHandle;
|
|
|
|
|
|
//
|
|
// Input buffer is not big enough to contain required input information.
|
|
//
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_HW_FIRMWARE_ACTIVATE)) {
|
|
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
//
|
|
// Input buffer basic validation.
|
|
//
|
|
if ((firmwareActivate->Version < sizeof(STORAGE_HW_FIRMWARE_ACTIVATE)) ||
|
|
(firmwareActivate->Size > irpStack->Parameters.DeviceIoControl.InputBufferLength)) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
//
|
|
// Only process the request for a supported port driver.
|
|
//
|
|
if (!ClassDeviceHwFirmwareIsPortDriverSupported(DeviceObject)) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
//
|
|
// Buffer "FunctionSupportInfo" is allocated during start device process. Following check defends against the situation
|
|
// of receiving this IOCTL when the device is created but not started, or device start failed but did not get removed yet.
|
|
//
|
|
if (commonExtension->IsFdo && (fdoExtension->FunctionSupportInfo == NULL)) {
|
|
|
|
status = STATUS_UNSUCCESSFUL;
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
//
|
|
// Process the situation that request should be forwarded to lower level.
|
|
//
|
|
if (!commonExtension->IsFdo) {
|
|
passDown = TRUE;
|
|
}
|
|
|
|
if ((firmwareActivate->Flags & STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER) != 0) {
|
|
passDown = TRUE;
|
|
}
|
|
|
|
if (passDown) {
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
FREE_POOL(Srb);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// If firmware information hasn't been cached in classpnp, retrieve it.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
goto Exit_Firmware_Activate;
|
|
} else {
|
|
//
|
|
// If this is the first time of retrieving firmware information,
|
|
// send request to lower level to get it.
|
|
//
|
|
status = ClasspGetHwFirmwareInfo(DeviceObject);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fail the request if the firmware information cannot be retrieved.
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
} else {
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
//
|
|
// Acquire the SyncLock to ensure the HwFirmwareInfo pointer doesn't change
|
|
// while we're dereferencing it.
|
|
//
|
|
lockHeld = TRUE;
|
|
KeAcquireInStackQueuedSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
|
|
|
|
//
|
|
// Validate the device support
|
|
//
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SupportUpgrade == FALSE) {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
//
|
|
// Check if the slot number is valid.
|
|
//
|
|
for (i = 0; i < fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount; i++) {
|
|
if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Slot[i].SlotNumber == firmwareActivate->Slot) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount) {
|
|
//
|
|
// Either the slot number is out of scope or the slot is read-only.
|
|
//
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
//
|
|
// We're done accessing HwFirmwareInfo at this point so we can release
|
|
// the SyncLock.
|
|
//
|
|
NT_ASSERT(lockHeld);
|
|
KeReleaseInStackQueuedSpinLock(&lockHandle);
|
|
lockHeld = FALSE;
|
|
|
|
//
|
|
// Process the request by translating it into WRITE BUFFER command.
|
|
//
|
|
//
|
|
// Setup the CDB. This should be an untagged request.
|
|
//
|
|
SrbSetCdbLength(Srb, CDB10GENERIC_LENGTH);
|
|
cdb = SrbGetCdb(Srb);
|
|
cdb->WRITE_BUFFER.OperationCode = SCSIOP_WRITE_DATA_BUFF;
|
|
cdb->WRITE_BUFFER.Mode = SCSI_WRITE_BUFFER_MODE_ACTIVATE_DEFERRED_MICROCODE;
|
|
cdb->WRITE_BUFFER.ModeSpecific = 0; //Reserved for Mode 0x0F
|
|
cdb->WRITE_BUFFER.BufferID = firmwareActivate->Slot; //NOTE: this field will be ignored by SCSI device.
|
|
|
|
//
|
|
// Set timeout value.
|
|
//
|
|
SrbSetTimeOutValue(Srb, FIRMWARE_ACTIVATE_TIMEOUT_VALUE);
|
|
|
|
//
|
|
// This routine uses a completion routine - ClassIoComplete() so we don't want to release
|
|
// the remove lock until then.
|
|
//
|
|
status = ClassSendSrbAsynchronous(DeviceObject,
|
|
Srb,
|
|
Irp,
|
|
NULL,
|
|
0,
|
|
FALSE);
|
|
|
|
if (status != STATUS_PENDING) {
|
|
//
|
|
// If the irp cannot be sent down, the Srb has been freed. NULL it to prevent from freeing it again.
|
|
//
|
|
Srb = NULL;
|
|
|
|
goto Exit_Firmware_Activate;
|
|
}
|
|
|
|
return status;
|
|
|
|
Exit_Firmware_Activate:
|
|
|
|
//
|
|
// Release the SyncLock if it's still held.
|
|
// This should only happen in the failure path.
|
|
//
|
|
if (lockHeld) {
|
|
KeReleaseInStackQueuedSpinLock(&lockHandle);
|
|
lockHeld = FALSE;
|
|
}
|
|
|
|
//
|
|
// Firmware Activate request will be failed.
|
|
//
|
|
NT_ASSERT(!NT_SUCCESS(status));
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
#else
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
Irp->IoStatus.Status = status;
|
|
#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
FREE_POOL(Srb);
|
|
return status;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ClasspIsThinProvisioningError (
|
|
_In_ PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks whether the completed SRB Srb was completed with a thin provisioning
|
|
soft threshold error.
|
|
|
|
Arguments:
|
|
|
|
Srb - the SRB to be checked.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN
|
|
|
|
--*/
|
|
{
|
|
if (TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) {
|
|
PVOID senseBuffer = SrbGetSenseInfoBuffer(Srb);
|
|
if (senseBuffer) {
|
|
UCHAR senseKey = 0;
|
|
UCHAR addlSenseCode = 0;
|
|
UCHAR addlSenseCodeQual = 0;
|
|
BOOLEAN validSense = ScsiGetSenseKeyAndCodes(senseBuffer,
|
|
SrbGetSenseInfoBufferLength(Srb),
|
|
SCSI_SENSE_OPTIONS_NONE,
|
|
&senseKey,
|
|
&addlSenseCode,
|
|
&addlSenseCodeQual);
|
|
|
|
return (validSense
|
|
&& (senseKey == SCSI_SENSE_UNIT_ATTENTION)
|
|
&& (addlSenseCode == SCSI_ADSENSE_LB_PROVISIONING)
|
|
&& (addlSenseCodeQual == SCSI_SENSEQ_SOFT_THRESHOLD_REACHED));
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|