https://git.reactos.org/?p=reactos.git;a=commitdiff;h=83b85e2124dcfc01a23516...
commit 83b85e2124dcfc01a23516d1c07b84f520507e88 Author: Victor Perevertkin victor.perevertkin@reactos.org AuthorDate: Fri Oct 16 04:37:10 2020 +0300 Commit: Victor Perevertkin victor.perevertkin@reactos.org CommitDate: Fri Oct 16 04:37:10 2020 +0300
[CDROM_NEW] Import Microsoft CDROM class driver from GitHub
The source code is licensed under MS-PL license, taken from Windows Driver Samples repository (microsoft/Windows-driver-samples@master/storage/class/cdrom/) Synched with commit 96eb96dfb613e4c745db6bd1f53a92fe7e2290fc The driver is written for Windows 10 and uses KMDF so we compile it with ntoskrnl_vista and wdf01000 statically linked (for wdf01000 this will likely be changed in future)
CORE-17129 --- drivers/storage/class/cdrom_new/CMakeLists.txt | 41 + drivers/storage/class/cdrom_new/aacs.c | 1481 ++++ drivers/storage/class/cdrom_new/autorun.c | 3080 ++++++++ drivers/storage/class/cdrom_new/cdrom.c | 4242 +++++++++++ drivers/storage/class/cdrom_new/cdrom.h | 1624 +++++ drivers/storage/class/cdrom_new/cdrom.inf | 159 + drivers/storage/class/cdrom_new/cdrom.rc | 23 + drivers/storage/class/cdrom_new/cdromp.h | 383 + drivers/storage/class/cdrom_new/common.c | 3878 ++++++++++ drivers/storage/class/cdrom_new/data.c | 283 + drivers/storage/class/cdrom_new/guid.c | 16 + drivers/storage/class/cdrom_new/init.c | 2742 +++++++ drivers/storage/class/cdrom_new/ioctl.c | 9134 ++++++++++++++++++++++++ drivers/storage/class/cdrom_new/ioctl.h | 847 +++ drivers/storage/class/cdrom_new/license.txt | 23 + drivers/storage/class/cdrom_new/localwpp.ini | 17 + drivers/storage/class/cdrom_new/mmc.c | 1629 +++++ drivers/storage/class/cdrom_new/mmc.h | 122 + drivers/storage/class/cdrom_new/pnppower.c | 689 ++ drivers/storage/class/cdrom_new/scratch.c | 1467 ++++ drivers/storage/class/cdrom_new/scratch.h | 187 + drivers/storage/class/cdrom_new/sense.c | 2600 +++++++ drivers/storage/class/cdrom_new/zpodd.c | 832 +++ 23 files changed, 35499 insertions(+)
diff --git a/drivers/storage/class/cdrom_new/CMakeLists.txt b/drivers/storage/class/cdrom_new/CMakeLists.txt new file mode 100644 index 00000000000..7def3bc655d --- /dev/null +++ b/drivers/storage/class/cdrom_new/CMakeLists.txt @@ -0,0 +1,41 @@ + +remove_definitions(-D_WIN32_WINNT=0x502) + +list(APPEND SOURCE + aacs.c + autorun.c + cdrom.c + common.c + data.c + guid.c + init.c + ioctl.c + mmc.c + pnppower.c + scratch.c + sense.c + zpodd.c + cdrom.h) + +add_library(cdrom MODULE ${SOURCE}) +set_module_type(cdrom kernelmodedriver) + +if(GCC OR CLANG) + target_compile_options(cdrom PRIVATE -Wno-format -Wno-unused-variable -Wno-pointer-sign) +endif() + +if(GCC) + target_compile_options(cdrom PRIVATE -Wno-unknown-pragmas -Wno-incompatible-pointer-types -Wno-switch) +endif() + +if(CLANG) + target_compile_options(cdrom PRIVATE -Wno-enum-conversion -Wno-tautological-constant-compare) +endif() + +target_compile_definitions(cdrom PRIVATE DEBUG_USE_KDPRINT) + +target_link_libraries(cdrom wdf01000 ntoskrnl_vista libcntpr ${PSEH_LIB}) +add_importlibs(cdrom ntoskrnl hal) +# add_pch(cdrom cdrom.h SOURCE) +add_cd_file(TARGET cdrom DESTINATION reactos/system32/drivers NO_CAB FOR all) +add_driver_inf(cdrom cdrom.inf) diff --git a/drivers/storage/class/cdrom_new/aacs.c b/drivers/storage/class/cdrom_new/aacs.c new file mode 100644 index 00000000000..f81eacb372c --- /dev/null +++ b/drivers/storage/class/cdrom_new/aacs.c @@ -0,0 +1,1481 @@ +/*-- + +Copyright (C) Microsoft Corporation. All rights reserved. + +Module Name: + + aacs.c + +Abstract: + + The CDROM class driver implementation of handling AACS IOCTLs. + +Environment: + + kernel mode only + +Notes: + + +Revision History: + +--*/ + +#include "stddef.h" +#include "string.h" + +#include "ntddk.h" +#include "ntddstor.h" +#include "cdrom.h" +#include "ioctl.h" +#include "scratch.h" + +#ifdef DEBUG_USE_WPP +#include "aacs.tmh" +#endif + +#ifdef ALLOC_PRAGMA + +#pragma alloc_text(PAGE, DeviceHandleAacsReadMediaKeyBlock) +#pragma alloc_text(PAGE, DeviceHandleAacsStartSession) +#pragma alloc_text(PAGE, DeviceHandleAacsEndSession) +#pragma alloc_text(PAGE, DeviceHandleAacsSendCertificate) +#pragma alloc_text(PAGE, DeviceHandleAacsGetCertificate) +#pragma alloc_text(PAGE, DeviceHandleAacsGetChallengeKey) +#pragma alloc_text(PAGE, DeviceHandleAacsReadSerialNumber) +#pragma alloc_text(PAGE, DeviceHandleAacsReadMediaId) +#pragma alloc_text(PAGE, DeviceHandleAacsReadBindingNonce) +#pragma alloc_text(PAGE, DeviceHandleAacsGenerateBindingNonce) +#pragma alloc_text(PAGE, DeviceHandleReadVolumeId) +#pragma alloc_text(PAGE, DeviceHandleSendChallengeKey) + +#endif + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsReadMediaKeyBlock( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTLs: + IOCTL_AACS_READ_MEDIA_KEY_BLOCK_SIZE + IOCTL_AACS_READ_MEDIA_KEY_BLOCK +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PAACS_LAYER_NUMBER layerNumber = NULL; + PVOID outputBuffer = NULL; + ULONG transferSize = sizeof(READ_DVD_STRUCTURES_HEADER); + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&layerNumber, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + if (RequestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_AACS_READ_MEDIA_KEY_BLOCK) + { + // maximum size for this transfer is one pack + header + transferSize += AACS_MKB_PACK_SIZE; + } + + if (transferSize > DeviceExtension->ScratchContext.ScratchBufferSize) + { + // rare case. normally the size of scratch buffer is 64k. + status = STATUS_INTERNAL_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + UCHAR rmdBlockNumber = 0; + BOOLEAN sendChangedCommand = TRUE; + BOOLEAN shouldRetry = TRUE; + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.READ_DVD_STRUCTURE.OperationCode = SCSIOP_READ_DVD_STRUCTURE; + // cdb->AsByte[1] = 0x01; // AACS sub-command not required for this + + cdb.READ_DVD_STRUCTURE.LayerNumber = (UCHAR)(*layerNumber); + cdb.READ_DVD_STRUCTURE.Format = 0x83; // MKB + cdb.READ_DVD_STRUCTURE.AllocationLength[0] = (UCHAR)(transferSize >> (8*1)); + cdb.READ_DVD_STRUCTURE.AllocationLength[1] = (UCHAR)(transferSize >> (8*0)); + + while (sendChangedCommand) + { + // RMDBlockNumber is set to zero.... + // RMDBlockNumber[3] maybe changed for other blocks. + cdb.READ_DVD_STRUCTURE.RMDBlockNumber[3] = rmdBlockNumber; + + if (shouldRetry) + { + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, transferSize, TRUE, &cdb, 12); + } + + #ifdef ENABLE_AACS_TESTING + if (RequestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_AACS_READ_MEDIA_KEY_BLOCK_SIZE) + { + static const UCHAR results[] = { 0x80, 0x02, 0x00, 0x02 }; + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; + } + else if (RequestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_AACS_READ_MEDIA_KEY_BLOCK) + { + static const UCHAR results[] = { 0x80, 0x02, 0x00, 0x02 }; + static const UCHAR defaultFill = 0x30; // '0' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x8004, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; + } + #endif //ENABLE_AACS_TESTING + + if (NT_SUCCESS(status)) + { + // command succeeded, process data... + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + UCHAR thisPackNumber = cdb.READ_DVD_STRUCTURE.RMDBlockNumber[3]; + UCHAR otherPacks = header->Reserved[1]; + + // validate and zero-base the otherPacks + if (otherPacks == 0) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "AACS: Device is reporting zero total packs (invalid)\n")); + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + else + { + otherPacks--; + + if (RequestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_AACS_READ_MEDIA_KEY_BLOCK_SIZE) + { + // if not already requested last pack, do so now + if (otherPacks != thisPackNumber) + { + // re-send the command for the other pack number. + // this is safe here because NT_SUCCESS() is TRUE, + // and all the rest of this routine does is SetHardError() + // and release of resources we're still about to use. + + // re-zero the output buffer + RtlZeroMemory(DeviceExtension->ScratchContext.ScratchBuffer, sizeof(READ_DVD_STRUCTURES_HEADER)); + + // modify the CDB to get the very last pack of the MKB + rmdBlockNumber = otherPacks; + + transferSize = sizeof(READ_DVD_STRUCTURES_HEADER); + + // keep items clean + ScratchBuffer_ResetItems(DeviceExtension, TRUE); + + // make sure the loop will be executed for modified command. + sendChangedCommand = TRUE; + shouldRetry = TRUE; + } + else + { + // this request already got the last pack + // so just interpret the data + REVERSE_SHORT(&header->Length); + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + else + { + ULONG totalSize = header->Length; + // subtract out any remaining bytes in the header + // to get the number of usable bytes in this pack + totalSize -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + totalSize += otherPacks * AACS_MKB_PACK_SIZE; + + // save the result and complete the request + *((PULONG)outputBuffer) = totalSize; + *DataLength = sizeof(ULONG); + status = STATUS_SUCCESS; + } + // This will exit the loop of sendChangedCommand + sendChangedCommand = FALSE; + shouldRetry = FALSE; + } + } + else if (RequestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_AACS_READ_MEDIA_KEY_BLOCK) + { + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + else + { + // success, how many bytes to copy for this pack? + ULONG totalSize = header->Length; + size_t originalBufferSize; + + // subtract out any remaining bytes in the header + // to get the number of usable bytes in this pack + totalSize -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // if not the final pack, this should be a full transfer per spec + NT_ASSERT( (totalSize == AACS_MKB_PACK_SIZE) || (thisPackNumber == otherPacks) ); + + // validate the user's buffer is large enough to accept the full data + originalBufferSize = RequestParameters.Parameters.DeviceIoControl.OutputBufferLength; + + if (originalBufferSize < (totalSize + (AACS_MKB_PACK_SIZE*thisPackNumber))) + { + // just return a slightly bigger-than-normal size + *DataLength = (otherPacks + 1)*AACS_MKB_PACK_SIZE; + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + PUCHAR whereToCopy; + // determine where to copy to the user's memory + whereToCopy = outputBuffer; + whereToCopy += AACS_MKB_PACK_SIZE * thisPackNumber; + + RtlCopyMemory(whereToCopy, header->Data, totalSize); + + // update the Information field here because we already + // have calculated the size of the block + *DataLength = totalSize + (AACS_MKB_PACK_SIZE * thisPackNumber); + status = STATUS_SUCCESS; + + // if there are more packs to get from the device, send it again.... + if (thisPackNumber != otherPacks) + { + // re-send the command for the next pack number. + // this is safe here because NT_SUCCESS() is TRUE, + // and all the rest of this routine does is SetHardError() + // and release of resources we're still about to use. + + // re-zero the output buffer + RtlZeroMemory(DeviceExtension->ScratchContext.ScratchBuffer, sizeof(READ_DVD_STRUCTURES_HEADER)); + + // modify the CDB to get the next pack of the MKB + rmdBlockNumber = cdb.READ_DVD_STRUCTURE.RMDBlockNumber[3]++; + + // modify the SRB to be resent + // + transferSize = AACS_MKB_PACK_SIZE + sizeof(READ_DVD_STRUCTURES_HEADER); + + // keep items clean + ScratchBuffer_ResetItems(DeviceExtension, FALSE); + + // make sure the loop will be executed for modified command. + sendChangedCommand = TRUE; + shouldRetry = TRUE; + } + else + { + // else, that was the end of the transfer, so just complete the request + sendChangedCommand = FALSE; + } + } + } + + } // end of IOCTL_AACS_READ_MEDIA_KEY_BLOCK + } + } // end of NT_SUCCESS(status) + + if (!NT_SUCCESS(status)) + { + // command failed. + sendChangedCommand = FALSE; + } + } //end of while (sendChangedCommand) + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsStartSession( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_START_SESSION +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsGetAgid + + NTSTATUS status = STATUS_SUCCESS; + PDVD_SESSION_ID sessionId = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&sessionId, + NULL); + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(CDVD_REPORT_AGID_DATA); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.REPORT_KEY.OperationCode = SCSIOP_REPORT_KEY; + cdb.AsByte[7] = 0x02; // AACS key class + cdb.REPORT_KEY.AllocationLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.REPORT_KEY.AllocationLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.REPORT_KEY.KeyFormat = 0x00; // DVD_REPORT_AGID? + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }; + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PCDVD_KEY_HEADER keyHeader = DeviceExtension->ScratchContext.ScratchBuffer; + PCDVD_REPORT_AGID_DATA keyData = (PCDVD_REPORT_AGID_DATA)keyHeader->Data; + + *sessionId = (DVD_SESSION_ID)(keyData->AGID); + *DataLength = sizeof(DVD_SESSION_ID); + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsEndSession( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_END_SESSION +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsReleaseAgid + + NTSTATUS status = STATUS_SUCCESS; + PDVD_SESSION_ID sessionId = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&sessionId, + NULL); + + if (NT_SUCCESS(status)) + { + ULONG transferSize = 0; + CDB cdb; + DVD_SESSION_ID currentSession = 0; + DVD_SESSION_ID limitSession = 0; + + if(*sessionId == DVD_END_ALL_SESSIONS) + { + currentSession = 0; + limitSession = MAX_COPY_PROTECT_AGID - 1; + } + else + { + currentSession = *sessionId; + limitSession = *sessionId; + } + + ScratchBuffer_BeginUse(DeviceExtension); + + do + { + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.SEND_KEY.OperationCode = SCSIOP_SEND_KEY; + cdb.AsByte[7] = 0x02; // AACS key class + cdb.SEND_KEY.AGID = (UCHAR)(currentSession); + cdb.SEND_KEY.KeyFormat = DVD_INVALIDATE_AGID; + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, transferSize, FALSE, &cdb, 12); + + currentSession++; + } while ((currentSession <= limitSession) && NT_SUCCESS(status)); + +#ifdef ENABLE_AACS_TESTING + status = STATUS_SUCCESS; +#endif + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsSendCertificate( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_SEND_CERTIFICATE +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsSendHostCertificate + + NTSTATUS status = STATUS_SUCCESS; + PAACS_SEND_CERTIFICATE input = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_CERTIFICATE); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + // copy the input buffer to the data buffer for the transfer + { + PCDVD_KEY_HEADER header = (PCDVD_KEY_HEADER)DeviceExtension->ScratchContext.ScratchBuffer; + ULONG tmp = dataTransferLength; + + tmp -= RTL_SIZEOF_THROUGH_FIELD(CDVD_KEY_HEADER, DataLength); + + header->DataLength[0] = (UCHAR)(tmp >> (8*1)); + header->DataLength[1] = (UCHAR)(tmp >> (8*0)); + RtlCopyMemory(header->Data, &(input->Certificate), sizeof(AACS_CERTIFICATE)); + } + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.SEND_KEY.OperationCode = SCSIOP_SEND_KEY; + cdb.AsByte[7] = 0x02; // AACS key class + cdb.SEND_KEY.ParameterListLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.SEND_KEY.ParameterListLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.SEND_KEY.AGID = (UCHAR)( input->SessionId ); + cdb.SEND_KEY.KeyFormat = 0x01; // Send Host Challenge Certificate + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, FALSE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + *DataLength = 0; + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsGetCertificate( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_GET_CERTIFICATE +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsGetDriveCertificate + + NTSTATUS status = STATUS_SUCCESS; + PDVD_SESSION_ID input = NULL; + PVOID outputBuffer = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_CERTIFICATE); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.REPORT_KEY.OperationCode = SCSIOP_REPORT_KEY; + cdb.AsByte[7] = 0x02; // AACS key class + cdb.REPORT_KEY.AllocationLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.REPORT_KEY.AllocationLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.REPORT_KEY.AGID = (UCHAR)(*input); + cdb.REPORT_KEY.KeyFormat = 0x01; // Return a drive certificate challenge + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x72, 0x00, 0x00 }; + static const UCHAR defaultFill = 0x31; // '1' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x0074, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG dataLengthToCopy = sizeof(AACS_CERTIFICATE); + + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < (sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length))) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + + if (NT_SUCCESS(status)) + { + // adjust data length to reflect only the addition data + header->Length -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // exit if the drive is returning an unexpected data size + if (header->Length != dataLengthToCopy) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // else copy the data to the user's buffer + RtlCopyMemory(outputBuffer, header->Data, dataLengthToCopy); + *DataLength = dataLengthToCopy; + } + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsGetChallengeKey( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_GET_CHALLENGE_KEY +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsGetChallengeKey + + NTSTATUS status = STATUS_SUCCESS; + PDVD_SESSION_ID input = NULL; + PVOID outputBuffer = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_CHALLENGE_KEY); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.REPORT_KEY.OperationCode = SCSIOP_REPORT_KEY; + cdb.AsByte[7] = 0x02; // AACS key class + cdb.REPORT_KEY.AllocationLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.REPORT_KEY.AllocationLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.REPORT_KEY.AGID = (UCHAR)(*input); + cdb.REPORT_KEY.KeyFormat = 0x02; // Return a drive certificate challenge + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x52, 0x00, 0x00 }; + static const UCHAR defaultFill = 0x32; // '2' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x0054, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG dataLengthToCopy = sizeof(AACS_CHALLENGE_KEY); + + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + + if (NT_SUCCESS(status)) + { + // adjust data length to reflect only the addition data + header->Length -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // exit if the drive is returning an unexpected data size + if (header->Length != dataLengthToCopy) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // else copy the data to the user's buffer + RtlCopyMemory(outputBuffer, header->Data, dataLengthToCopy); + *DataLength = dataLengthToCopy; + } + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleSendChallengeKey( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_SEND_CHALLENGE_KEY +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsSendChallengeKey + + NTSTATUS status = STATUS_SUCCESS; + PAACS_SEND_CHALLENGE_KEY input = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_CHALLENGE_KEY); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + // copy the input buffer to the data buffer for the transfer + { + PCDVD_KEY_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG tmp = dataTransferLength; + tmp -= RTL_SIZEOF_THROUGH_FIELD(CDVD_KEY_HEADER, DataLength); + + header->DataLength[0] = (UCHAR)(tmp >> (8*1)); + header->DataLength[1] = (UCHAR)(tmp >> (8*0)); + RtlCopyMemory(header->Data, &(input->ChallengeKey), sizeof(AACS_CHALLENGE_KEY)); + } + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.SEND_KEY.OperationCode = SCSIOP_SEND_KEY; + cdb.AsByte[7] = 0x02; // AACS key class + cdb.SEND_KEY.ParameterListLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.SEND_KEY.ParameterListLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.SEND_KEY.AGID = (UCHAR)( input->SessionId ); + cdb.SEND_KEY.KeyFormat = 0x02; // Send Host Challenge Certificate + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, FALSE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + *DataLength = 0; + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleReadVolumeId( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_READ_VOLUME_ID +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsReadVolumeID + + NTSTATUS status = STATUS_SUCCESS; + PDVD_SESSION_ID input = NULL; + PVOID outputBuffer = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_VOLUME_ID); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.READ_DVD_STRUCTURE.OperationCode = SCSIOP_READ_DVD_STRUCTURE; + cdb.READ_DVD_STRUCTURE.Format = 0x80; // Return the AACS volumeID + cdb.READ_DVD_STRUCTURE.AllocationLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.READ_DVD_STRUCTURE.AllocationLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.READ_DVD_STRUCTURE.AGID = (UCHAR)(*input); + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x22, 0x00, 0x00 }; + static const UCHAR defaultFill = 0x33; // '3' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x0024, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG dataLengthToCopy = sizeof(AACS_VOLUME_ID); + + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + + if (NT_SUCCESS(status)) + { + // adjust data length to reflect only the addition data + header->Length -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // exit if the drive is returning an unexpected data size + if (header->Length != dataLengthToCopy) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // else copy the data to the user's buffer + RtlCopyMemory(outputBuffer, header->Data, dataLengthToCopy); + *DataLength = dataLengthToCopy; + } + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsReadSerialNumber( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_READ_SERIAL_NUMBER +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsReadSerialNumber + + NTSTATUS status = STATUS_SUCCESS; + PDVD_SESSION_ID input = NULL; + PVOID outputBuffer = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_SERIAL_NUMBER); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.READ_DVD_STRUCTURE.OperationCode = SCSIOP_READ_DVD_STRUCTURE; + cdb.READ_DVD_STRUCTURE.Format = 0x81; // Return the AACS volumeID + cdb.READ_DVD_STRUCTURE.AllocationLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.READ_DVD_STRUCTURE.AllocationLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.READ_DVD_STRUCTURE.AGID = (UCHAR)(*input); + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x22, 0x00, 0x00 }; + static const UCHAR defaultFill = 0x34; // '4' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x0024, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG dataLengthToCopy = sizeof(AACS_SERIAL_NUMBER); + + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + + if (NT_SUCCESS(status)) + { + // adjust data length to reflect only the addition data + header->Length -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // exit if the drive is returning an unexpected data size + if (header->Length != dataLengthToCopy) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // else copy the data to the user's buffer + RtlCopyMemory(outputBuffer, header->Data, dataLengthToCopy); + *DataLength = dataLengthToCopy; + } + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsReadMediaId( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_READ_MEDIA_ID +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsReadMediaID + + NTSTATUS status = STATUS_SUCCESS; + PDVD_SESSION_ID input = NULL; + PVOID outputBuffer = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_MEDIA_ID); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.READ_DVD_STRUCTURE.OperationCode = SCSIOP_READ_DVD_STRUCTURE; + cdb.READ_DVD_STRUCTURE.Format = 0x82; // Return the AACS volumeID + cdb.READ_DVD_STRUCTURE.AllocationLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.READ_DVD_STRUCTURE.AllocationLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.READ_DVD_STRUCTURE.AGID = (UCHAR)(*input); + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x22, 0x00, 0x00 }; + static const UCHAR defaultFill = 0x35; // '5' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x0024, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG dataLengthToCopy = sizeof(AACS_MEDIA_ID); + + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + + if (NT_SUCCESS(status)) + { + // adjust data length to reflect only the addition data + header->Length -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // exit if the drive is returning an unexpected data size + if (header->Length != dataLengthToCopy) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // else copy the data to the user's buffer + RtlCopyMemory(outputBuffer, header->Data, dataLengthToCopy); + *DataLength = dataLengthToCopy; + } + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsReadBindingNonce( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_READ_BINDING_NONCE +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsReadBindingNonce + + NTSTATUS status = STATUS_SUCCESS; + PAACS_READ_BINDING_NONCE input = NULL; + PVOID outputBuffer = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_BINDING_NONCE); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + cdb.REPORT_KEY.OperationCode = SCSIOP_REPORT_KEY; + cdb.REPORT_KEY.LogicalBlockAddress[0] = (UCHAR)( input->StartLba >> (3*8) ); + cdb.REPORT_KEY.LogicalBlockAddress[1] = (UCHAR)( input->StartLba >> (2*8) ); + cdb.REPORT_KEY.LogicalBlockAddress[2] = (UCHAR)( input->StartLba >> (1*8) ); + cdb.REPORT_KEY.LogicalBlockAddress[3] = (UCHAR)( input->StartLba >> (0*8) ); + cdb.AsByte[6] = (UCHAR)( input->NumberOfSectors ); + cdb.AsByte[7] = 0x02; // AACS key class + cdb.REPORT_KEY.AllocationLength[0] = (UCHAR)(dataTransferLength >> (8*1)); + cdb.REPORT_KEY.AllocationLength[1] = (UCHAR)(dataTransferLength >> (8*0)); + cdb.REPORT_KEY.AGID = (UCHAR)( input->SessionId ); + cdb.REPORT_KEY.KeyFormat = 0x21; // Return an existing binding nonce + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x22, 0x00, 0x00 }; + static const UCHAR defaultFill = 0x36; // '6' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x0024, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG dataLengthToCopy = sizeof(AACS_BINDING_NONCE); + + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + + if (NT_SUCCESS(status)) + { + // adjust data length to reflect only the addition data + header->Length -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // exit if the drive is returning an unexpected data size + if (header->Length != dataLengthToCopy) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // else copy the data to the user's buffer + RtlCopyMemory(outputBuffer, header->Data, dataLengthToCopy); + *DataLength = dataLengthToCopy; + } + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceHandleAacsGenerateBindingNonce( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + This routine is used to process IOCTL: + IOCTL_AACS_GENERATE_BINDING_NONCE +Arguments: + DeviceExtension - device context + + Request - the request that will be formatted + + RequestParameters - request parameter structur + + DataLength - data transferred length + +Return Value: + NTSTATUS + + --*/ +{ + //AacsGenerateBindingNonce + + NTSTATUS status = STATUS_SUCCESS; + PAACS_READ_BINDING_NONCE input = NULL; + PVOID outputBuffer = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&input, + NULL); + + if (NT_SUCCESS(status)) + { + status = WdfRequestRetrieveOutputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.OutputBufferLength, + (PVOID*)&outputBuffer, + NULL); + } + + if (NT_SUCCESS(status)) + { + ULONG dataTransferLength = sizeof(CDVD_KEY_HEADER) + sizeof(AACS_BINDING_NONCE); + CDB cdb; + + ScratchBuffer_BeginUse(DeviceExtension); + + RtlZeroMemory(&cdb, sizeof(CDB)); + // Set up the CDB + + status = ScratchBuffer_ExecuteCdb(DeviceExtension, Request, dataTransferLength, TRUE, &cdb, 12); + +#ifdef ENABLE_AACS_TESTING + static const UCHAR results[] = { 0x00, 0x22, 0x00, 0x00 }; + static const UCHAR defaultFill = 0x37; // '7' + RtlFillMemory(DeviceExtension->ScratchContext.ScratchBuffer, 0x0024, defaultFill); + RtlCopyMemory(DeviceExtension->ScratchContext.ScratchBuffer, results, SIZEOF_ARRAY(results)); + status = STATUS_SUCCESS; +#endif + if (NT_SUCCESS(status)) + { + PDVD_DESCRIPTOR_HEADER header = DeviceExtension->ScratchContext.ScratchBuffer; + ULONG dataLengthToCopy = sizeof(AACS_BINDING_NONCE); + + // make length field native byte ordering + REVERSE_SHORT(&header->Length); + + // exit if getting invalid data from the drive + if (header->Length < sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length)) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + + if (NT_SUCCESS(status)) + { + // adjust data length to reflect only the addition data + header->Length -= sizeof(DVD_DESCRIPTOR_HEADER) - RTL_SIZEOF_THROUGH_FIELD(DVD_DESCRIPTOR_HEADER, Length); + + // exit if the drive is returning an unexpected data size + if (header->Length != dataLengthToCopy) + { + *DataLength = 0; + status = STATUS_IO_DEVICE_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // else copy the data to the user's buffer + RtlCopyMemory(outputBuffer, header->Data, dataLengthToCopy); + *DataLength = dataLengthToCopy; + } + } + + ScratchBuffer_EndUse(DeviceExtension); + } + + return status; +} + diff --git a/drivers/storage/class/cdrom_new/autorun.c b/drivers/storage/class/cdrom_new/autorun.c new file mode 100644 index 00000000000..e5c9b110021 --- /dev/null +++ b/drivers/storage/class/cdrom_new/autorun.c @@ -0,0 +1,3080 @@ +/*++ + +Copyright (C) Microsoft Corporation. All rights reserved. + +Module Name: + + autorun.c + +Abstract: + + Code for support of media change detection in the cd/dvd driver + +Environment: + + kernel mode only + +Notes: + + +Revision History: + +--*/ + +#include "stddef.h" +#include "string.h" + +#include "ntddk.h" +#include "ntddstor.h" +#include "cdrom.h" +#include "mmc.h" +#include "ioctl.h" + +#include "ntstrsafe.h" + +#ifdef DEBUG_USE_WPP +#include "autorun.tmh" +#endif + +#define GESN_TIMEOUT_VALUE (0x4) +#define GESN_BUFFER_SIZE (0x8) +#define GESN_DEVICE_BUSY_LOWER_THRESHOLD_100_MS (2) + +#define MAXIMUM_IMMEDIATE_MCN_RETRIES (0x20) +#define MCN_REG_SUBKEY_NAME (L"MediaChangeNotification") +#define MCN_REG_AUTORUN_DISABLE_INSTANCE_NAME (L"AlwaysDisableMCN") +#define MCN_REG_AUTORUN_ENABLE_INSTANCE_NAME (L"AlwaysEnableMCN") + +// +// Only send polling irp when device is fully powered up and a +// power down irp is not in progress. +// +// NOTE: This helps close a window in time where a polling irp could cause +// a drive to spin up right after it has powered down. The problem is +// that SCSIPORT, ATAPI and SBP2 will be in the process of powering +// down (which may take a few seconds), but won't know that. It would +// then get a polling irp which will be put into its queue since it +// the disk isn't powered down yet. Once the disk is powered down it +// will find the polling irp in the queue and then power up the +// device to do the poll. They do not want to check if the polling +// irp has the SRB_NO_KEEP_AWAKE flag here since it is in a critical +// path and would slow down all I/Os. A better way to fix this +// would be to serialize the polling and power down irps so that +// only one of them is sent to the device at a time. +// + +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +DeviceIsMediaChangeDisabledDueToHardwareLimitation( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +DeviceIsMediaChangeDisabledForClass( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceMediaChangeDeviceInstanceOverride( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _Out_ PBOOLEAN Enabled + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceInitializeMcn( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ BOOLEAN AllowDriveToSleep + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceInitializeGesn( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ); + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +GesnDataInterpret( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ PNOTIFICATION_EVENT_STATUS_HEADER Header, + _Out_ PBOOLEAN ResendImmediately + ); + +RTL_QUERY_REGISTRY_ROUTINE DeviceMediaChangeRegistryCallBack; + +EVT_WDF_WORKITEM DeviceDisableGesn; + +#if ALLOC_PRAGMA + +#pragma alloc_text(PAGE, DeviceInitializeMediaChangeDetection) +#pragma alloc_text(PAGE, DeviceEnableMediaChangeDetection) +#pragma alloc_text(PAGE, DeviceDisableMediaChangeDetection) +#pragma alloc_text(PAGE, DeviceSendDelayedMediaChangeNotifications) +#pragma alloc_text(PAGE, DeviceReleaseMcnResources) +#pragma alloc_text(PAGE, DeviceMediaChangeRegistryCallBack) +#pragma alloc_text(PAGE, DeviceInitializeMcn) +#pragma alloc_text(PAGE, DeviceDisableGesn) + +#pragma alloc_text(PAGE, DeviceIsMediaChangeDisabledDueToHardwareLimitation) +#pragma alloc_text(PAGE, DeviceMediaChangeDeviceInstanceOverride) +#pragma alloc_text(PAGE, DeviceIsMediaChangeDisabledForClass) + +#pragma alloc_text(PAGE, DeviceDisableMainTimer) + +#pragma alloc_text(PAGE, GesnDataInterpret) + +#pragma alloc_text(PAGE, RequestSetupMcnRequest) +#pragma alloc_text(PAGE, RequestPostWorkMcnRequest) +#pragma alloc_text(PAGE, RequestSendMcnRequest) + +// +// DeviceEnableMainTimer is called by EvtDeviceD0Entry which can't be made pageable +// so neither is DeviceEnableMainTimer +// +//#pragma alloc_text(PAGE, DeviceEnableMainTimer) + +#pragma alloc_text(PAGE, DeviceInitializeGesn) + +#pragma alloc_text(PAGE, RequestHandleMcnControl) + +#endif + +#pragma warning(push) +#pragma warning(disable:4152) // nonstandard extension, function/data pointer conversion in expression + + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +GesnDataInterpret( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ PNOTIFICATION_EVENT_STATUS_HEADER Header, + _Out_ PBOOLEAN ResendImmediately + ) +/*++ + +Routine Description: + + This routine will interpret the data returned for a GESN command, and + (if appropriate) set the media change event, and broadcast the + appropriate events to user mode for applications who care. + +Arguments: + + DeviceExtension - the device extension + + Header - the resulting data from a GESN event. + requires at least EIGHT valid bytes (header == 4, data == 4) + + ResendImmediately - whether or not to immediately resend the request. + this should be FALSE if there was no event, FALSE if the reported + event was of the DEVICE BUSY class, else true. + +Return Value: + + STATUS_SUCCESS if successful, an error code otherwise + +Notes: + + DataBuffer must be at least four bytes of valid data (header == 4 bytes), + and have at least eight bytes of allocated memory (all events == 4 bytes). + + The call to StartNextPacket may occur before this routine is completed. + the operational change notifications are informational in nature, and + while useful, are not neccessary to ensure proper operation. For example, + if the device morphs to no longer supporting WRITE commands, all further + write commands will fail. There exists a small timing window wherein + IOCTL_IS_DISK_WRITABLE may be called and get an incorrect response. If + a device supports software write protect, it is expected that the + application can handle such a case. + + NOTE: perhaps setting the updaterequired byte to one should be done here. + if so, it relies upon the setting of a 32-byte value to be an atomic + operation. unfortunately, there is no simple way to notify a class driver + which wants to know that the device behavior requires updating. + + Not ready events may be sent every second. For example, if we were + to minimize the number of asynchronous notifications, an application may + register just after a large busy time was reported. This would then + prevent the application from knowing the device was busy until some + arbitrarily chosen timeout has occurred. Also, the GESN request would + have to still occur, since it checks for non-busy events (such as user + keybutton presses and media change events) as well. The specification + states that the lower-numered events get reported first, so busy events, + while repeating, will only be reported when all other events have been + cleared from the device. + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + LONG dataLength = 0; + LONG requiredLength = 0; + BOOLEAN inHomePosition = FALSE; + + PAGED_CODE(); + + // note: don't allocate anything in this routine so that we can + // always just 'return'. + *ResendImmediately = FALSE; + + if (Header->NEA) + { + return status; + } + if (Header->NotificationClass == NOTIFICATION_NO_CLASS_EVENTS) + { + return status; + } + + // HACKHACK - REF #0001 + // This loop is only taken initially, due to the inability to reliably + // auto-detect drives that report events correctly at boot. When we + // detect this behavior during the normal course of running, we will + // disable the hack, allowing more efficient use of the system. This + // should occur "nearly" instantly, as the drive should have multiple + // events queue'd (ie. power, morphing, media). + if (info->Gesn.HackEventMask) + { + // all events use the low four bytes of zero to indicate + // that there was no change in status. + UCHAR thisEvent = Header->ClassEventData[0] & 0xf; + UCHAR lowestSetBit; + UCHAR thisEventBit = (1 << Header->NotificationClass); + + if (!TEST_FLAG(info->Gesn.EventMask, thisEventBit)) + { + // The drive is reporting an event that wasn't requested + return STATUS_DEVICE_PROTOCOL_ERROR; + } + + // some bit magic here... this results in the lowest set bit only + lowestSetBit = info->Gesn.EventMask; + lowestSetBit &= (info->Gesn.EventMask - 1); + lowestSetBit ^= (info->Gesn.EventMask); + + if (thisEventBit != lowestSetBit) + { + // HACKHACK - REF #0001 + // the first time we ever see an event set that is not the lowest + // set bit in the request (iow, highest priority), we know that the + // hack is no longer required, as the device is ignoring "no change" + // events when a real event is waiting in the other requested queues. + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN::NONE: Compliant drive found, " + "removing GESN hack (%x, %x)\n", + thisEventBit, info->Gesn.EventMask)); + + info->Gesn.HackEventMask = FALSE; + } + else if (thisEvent == 0) // NOTIFICATION_*_EVENT_NO_CHANGE + { + // HACKHACK - REF #0001 + // note: this hack prevents poorly implemented firmware from constantly + // returning "No Event". we do this by cycling through the + // supported list of events here. + SET_FLAG(info->Gesn.NoChangeEventMask, thisEventBit); + CLEAR_FLAG(info->Gesn.EventMask, thisEventBit); + + // if we have cycled through all supported event types, then + // we need to reset the events we are asking about. else we + // want to resend this request immediately in case there was + // another event pending. + if (info->Gesn.EventMask == 0) + { + info->Gesn.EventMask = info->Gesn.NoChangeEventMask; + info->Gesn.NoChangeEventMask = 0; + } + else + { + *ResendImmediately = TRUE; + } + return status; + } + + } // end if (info->Gesn.HackEventMask) + + dataLength = (Header->EventDataLength[0] << 8) | + (Header->EventDataLength[1] & 0xff); + dataLength -= 2; + requiredLength = 4; // all events are four bytes + + if (dataLength < requiredLength) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "error - GESN returned only %x bytes data for fdo %p\n", + dataLength, DeviceExtension->DeviceObject)); + + return STATUS_DEVICE_PROTOCOL_ERROR; + } + + if (dataLength > requiredLength) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "error - GESN returned too many (%x) bytes data for fdo %p\n", + dataLength, DeviceExtension->DeviceObject)); + } + + if ((Header->ClassEventData[0] & 0xf) == 0) + { + // a zero event is a "no change event, so do not retry + return status; + } + + // because a event other than "no change" occurred, + // we should immediately resend this request. + *ResendImmediately = TRUE; + + switch (Header->NotificationClass) + { + + case NOTIFICATION_OPERATIONAL_CHANGE_CLASS_EVENTS: // 0x01 + { + PNOTIFICATION_OPERATIONAL_STATUS opChangeInfo = + (PNOTIFICATION_OPERATIONAL_STATUS)(Header->ClassEventData); + ULONG event; + + if (opChangeInfo->OperationalEvent == NOTIFICATION_OPERATIONAL_EVENT_CHANGE_REQUESTED) + { + break; + } + + event = (opChangeInfo->Operation[0] << 8) | + (opChangeInfo->Operation[1] ) ; + + // Workaround some hardware that is buggy but prevalent in the market + // This hardware has the property that it will report OpChange events repeatedly, + // causing us to retry immediately so quickly that we will eventually disable + // GESN to prevent an infinite loop. + // (only one valid OpChange event type now, only two ever defined) + if (info->MediaChangeRetryCount >= 4) + { + // + // HACKHACK - REF #0002 + // Some drives incorrectly report OpChange/Change (001b/0001h) events + // continuously when the tray has been ejected. This causes this routine + // to set ResendImmediately to "TRUE", and that results in our cycling + // 32 times immediately resending. At that point, we give up detecting + // the infinite retry loop, and disable GESN on these drives. This + // prevents Media Eject Request (from eject button) from being reported. + // Thus, instead we should attempt to workaround this issue by detecting + // this behavior. + // + + static UCHAR const OpChangeMask = 0x02; + + // At least one device reports "temporarily busy" (which is useless) on eject + // At least one device reports "OpChange" repeatedly when re-inserting media + // All seem to work well using this workaround + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_MCN, + "GESN OpChange events are broken. Working around this problem in software (for WDFDEVICE %p)\n", + DeviceExtension->Device)); + + // OpChange is not the only bit set -- Media class is required.... + NT_ASSERT(CountOfSetBitsUChar(info->Gesn.EventMask) != 1); + + // Force the use of the hackhack (ref #0001) to workaround the + // issue noted this hackhack (ref #0002). + SET_FLAG(info->Gesn.NoChangeEventMask, OpChangeMask); + CLEAR_FLAG(info->Gesn.EventMask, OpChangeMask); + info->Gesn.HackEventMask = TRUE; + + // don't request the opChange event again. use the method + // defined by hackhack (ref #0001) as the workaround. + if (info->Gesn.EventMask == 0) + { + info->Gesn.EventMask = info->Gesn.NoChangeEventMask; + info->Gesn.NoChangeEventMask = 0; + *ResendImmediately = FALSE; + } + else + { + *ResendImmediately = TRUE; + } + + break; + } + + + if ((event == NOTIFICATION_OPERATIONAL_OPCODE_FEATURE_ADDED) | + (event == NOTIFICATION_OPERATIONAL_OPCODE_FEATURE_CHANGE)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN says features added/changed for WDFDEVICE %p\n", + DeviceExtension->Device)); + + // don't notify that new media arrived, just set the + // DO_VERIFY to force a FS reload. + + if (IsVolumeMounted(DeviceExtension->DeviceObject)) + { + SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME); + } + + // Call error handler with + // a "fake" media change error in case it needs to update + // internal structures as though a media change occurred. + { + SCSI_REQUEST_BLOCK srb = {0}; + SENSE_DATA sense = {0}; + NTSTATUS tempStatus; + BOOLEAN retry; + + tempStatus = STATUS_MEDIA_CHANGED; + retry = FALSE; + + srb.CdbLength = 6; + srb.Length = sizeof(SCSI_REQUEST_BLOCK); + srb.SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR; + srb.SenseInfoBuffer = &sense; + srb.SenseInfoBufferLength = sizeof(SENSE_DATA); + + sense.AdditionalSenseLength = sizeof(SENSE_DATA) - + RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength); + + sense.SenseKey = SCSI_SENSE_UNIT_ATTENTION; + sense.AdditionalSenseCode = SCSI_ADSENSE_MEDIUM_CHANGED; + + if (DeviceExtension->DeviceAdditionalData.ErrorHandler) + { + DeviceExtension->DeviceAdditionalData.ErrorHandler(DeviceExtension, + &srb, + &tempStatus, + &retry); + } + } // end error handler + + } + break; + } + + case NOTIFICATION_EXTERNAL_REQUEST_CLASS_EVENTS: // 0x3 + { + PNOTIFICATION_EXTERNAL_STATUS externalInfo = + (PNOTIFICATION_EXTERNAL_STATUS)(Header->ClassEventData); + DEVICE_EVENT_EXTERNAL_REQUEST externalData = {0}; + + // unfortunately, due to time constraints, we will only notify + // about keys being pressed, and not released. this makes keys + // single-function, but simplifies the code significantly. + if (externalInfo->ExternalEvent != NOTIFICATION_EXTERNAL_EVENT_BUTTON_DOWN) + { + break; + } + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN::EXTERNAL: Event: %x Status %x Req %x\n", + externalInfo->ExternalEvent, externalInfo->ExternalStatus, + (externalInfo->Request[0] << 8) | externalInfo->Request[1] + )); + + externalData.Version = 1; + externalData.DeviceClass = 0; + externalData.ButtonStatus = externalInfo->ExternalEvent; + externalData.Request = (externalInfo->Request[0] << 8) | + (externalInfo->Request[1] & 0xff); + KeQuerySystemTime(&(externalData.SystemTime)); + externalData.SystemTime.QuadPart *= (LONGLONG)KeQueryTimeIncrement(); + + DeviceSendNotification(DeviceExtension, + &GUID_IO_DEVICE_EXTERNAL_REQUEST, + sizeof(DEVICE_EVENT_EXTERNAL_REQUEST), + &externalData); + + return status; + } + + case NOTIFICATION_MEDIA_STATUS_CLASS_EVENTS: // 0x4 + { + PNOTIFICATION_MEDIA_STATUS mediaInfo = + (PNOTIFICATION_MEDIA_STATUS)(Header->ClassEventData); + + if ((mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_NEW_MEDIA) || + (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_CHANGE)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN::MEDIA ARRIVAL, Status %x\n", + mediaInfo->MediaStatus)); + + if (IsVolumeMounted(DeviceExtension->DeviceObject)) + { + SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME); + } + DeviceSetMediaChangeStateEx(DeviceExtension, + MediaPresent, + NULL); + + // If media is inserted into slot loading type, mark the device active + // to not power off. + if ((DeviceExtension->ZeroPowerODDInfo != NULL) && + (DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_CADDY) && + (DeviceExtension->ZeroPowerODDInfo->Load == 0)) // Slot + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, + "GesnDataInterpret: MediaArrival event detected, device marked as active\n")); + + DeviceMarkActive(DeviceExtension, TRUE, FALSE); + } + } + else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_REMOVAL) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN::MEDIA REMOVAL, Status %x\n", + mediaInfo->MediaStatus)); + + DeviceSetMediaChangeStateEx(DeviceExtension, + MediaNotPresent, + NULL); + + // If media is removed from slot loading type, start powering off the device + // if it is ZPODD capable. + if ((DeviceExtension->ZeroPowerODDInfo != NULL) && + (DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_CADDY) && + (DeviceExtension->ZeroPowerODDInfo->Load == 0)) // Slot + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, + "GesnDataInterpret: MediaRemoval event detected, device marked as idle\n")); + + DeviceMarkActive(DeviceExtension, FALSE, FALSE); + } + } + else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_EJECT_REQUEST) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN::MEDIA EJECTION, Status %x\n", + mediaInfo->MediaStatus)); + + DeviceSendNotification(DeviceExtension, + &GUID_IO_MEDIA_EJECT_REQUEST, + 0, + NULL); + } + + break; + } + + case NOTIFICATION_DEVICE_BUSY_CLASS_EVENTS: // lowest priority events... + { + PNOTIFICATION_BUSY_STATUS busyInfo = + (PNOTIFICATION_BUSY_STATUS)(Header->ClassEventData); + DEVICE_EVENT_BECOMING_READY busyData = {0}; + + // else we want to report the approximated time till it's ready. + busyData.Version = 1; + busyData.Reason = busyInfo->DeviceBusyStatus; + busyData.Estimated100msToReady = (busyInfo->Time[0] << 8) | + (busyInfo->Time[1] & 0xff); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN::BUSY: Event: %x Status %x Time %x\n", + busyInfo->DeviceBusyEvent, busyInfo->DeviceBusyStatus, + busyData.Estimated100msToReady + )); + + // Ignore the notification if the time is small + if (busyData.Estimated100msToReady >= GESN_DEVICE_BUSY_LOWER_THRESHOLD_100_MS) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GesnDataInterpret: media BECOMING_READY\n")); + + DeviceSendNotification(DeviceExtension, + &GUID_IO_DEVICE_BECOMING_READY, + sizeof(DEVICE_EVENT_BECOMING_READY), + &busyData); + } + + // If manual loading operation is observed for slot loading type, start powering off the device + // if it is ZPODD capable. + if ((DeviceExtension->ZeroPowerODDInfo != NULL) && + (DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_TRAY) && + (DeviceExtension->ZeroPowerODDInfo->Load == 0) && // Drawer + (busyInfo->DeviceBusyEvent == NOTIFICATION_BUSY_EVENT_LO_CHANGE) && + (busyInfo->DeviceBusyStatus == NOTIFICATION_BUSY_STATUS_NO_EVENT)) + { + inHomePosition = DeviceZPODDIsInHomePosition(DeviceExtension); + + if (inHomePosition == FALSE) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, + "GesnDataInterpret: LoChange event detected, device marked as active\n")); + + DeviceMarkActive(DeviceExtension, TRUE, FALSE); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, + "GesnDataInterpret: LoChange event detected, device marked as idle\n")); + + DeviceMarkActive(DeviceExtension, FALSE, FALSE); + } + } + + break; + } + + default: + { + break; + } + + } // end switch on notification class + + return status; +} + + +VOID +DeviceInternalSetMediaChangeState( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ MEDIA_CHANGE_DETECTION_STATE NewState, + _Inout_opt_ PMEDIA_CHANGE_DETECTION_STATE OldState + ) +/*++ + +Routine Description: + + This routine will (if appropriate) set the media change event for the + device. The event will be set if the media state is changed and + media change events are enabled. Otherwise the media state will be + tracked but the event will not be set. + + This routine will lock out the other media change routines if possible + but if not a media change notification may be lost after the enable has + been completed. + +Arguments: + + DeviceExtension - the device extension + + NewState - new state for setting + + OldState - optional storage for the old state + +Return Value: + + none + +--*/ +{ +#if DBG + LPCSTR states[] = {"Unknown", "Present", "Not Present", "Unavailable"}; +#endif + MEDIA_CHANGE_DETECTION_STATE oldMediaState; + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + CLASS_MEDIA_CHANGE_CONTEXT mcnContext; + + if (!((NewState >= MediaUnknown) && (NewState <= MediaUnavailable))) + { + return; + } + + if (info == NULL) + { + return; + } + + oldMediaState = info->LastKnownMediaDetectionState; + if (OldState) + { + *OldState = oldMediaState; + } + + info->LastKnownMediaDetectionState = NewState; + + // Increment MediaChangeCount on transition to MediaPresent + if (NewState == MediaPresent && oldMediaState != NewState) + { + InterlockedIncrement((PLONG)&DeviceExtension->MediaChangeCount); + } + + if (info->MediaChangeDetectionDisableCount != 0) + { +#if DBG + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInternalSetMediaChangeState: MCN not enabled, state " + "changed from %s to %s\n", + states[oldMediaState], states[NewState])); +#endif + return; + } +#if DBG + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInternalSetMediaChangeState: State change from %s to %s\n", + states[oldMediaState], states[NewState])); +#endif + + if (info->LastReportedMediaDetectionState == info->LastKnownMediaDetectionState) + { + // Media is in the same state as we reported last time, no need to report again. + return; + } + + // make the data useful -- it used to always be zero. + mcnContext.MediaChangeCount = DeviceExtension->MediaChangeCount; + mcnContext.NewState = NewState; + + if (NewState == MediaPresent) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInternalSetMediaChangeState: Reporting media ARRIVAL\n")); + + DeviceSendNotification(DeviceExtension, + &GUID_IO_MEDIA_ARRIVAL, + sizeof(CLASS_MEDIA_CHANGE_CONTEXT), + &mcnContext); + } + else if ((NewState == MediaNotPresent) || (NewState == MediaUnavailable)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInternalSetMediaChangeState: Reporting media REMOVAL\n")); + DeviceSendNotification(DeviceExtension, + &GUID_IO_MEDIA_REMOVAL, + sizeof(CLASS_MEDIA_CHANGE_CONTEXT), + &mcnContext); + } + else + { + // Don't notify of changed going to unknown. + return; + } + + info->LastReportedMediaDetectionState = info->LastKnownMediaDetectionState; + + return; +} // end DeviceInternalSetMediaChangeState() + + +VOID +DeviceSetMediaChangeStateEx( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ MEDIA_CHANGE_DETECTION_STATE NewState, + _Inout_opt_ PMEDIA_CHANGE_DETECTION_STATE OldState + ) +/*++ + +Routine Description: + + This routine will (if appropriate) set the media change event for the + device. The event will be set if the media state is changed and + media change events are enabled. Otherwise the media state will be + tracked but the event will not be set. + +Arguments: + + DeviceExtension - the device extension + + NewState - new state for setting + + OldState - optional storage for the old state + +Return Value: + + none + +--*/ +{ + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + LARGE_INTEGER zero; + NTSTATUS status; + + // timeout value must be 0, as this function can be called at DISPATCH_LEVEL. + zero.QuadPart = 0; + + if (info == NULL) + { + return; + } + + status = KeWaitForMutexObject(&info->MediaChangeMutex, + Executive, + KernelMode, + FALSE, + &zero); + + if (status == STATUS_TIMEOUT) + { + // Someone else is in the process of setting the media state. + return; + } + + // Change the media present state and signal an event, if applicable + DeviceInternalSetMediaChangeState(DeviceExtension, NewState, OldState); + + KeReleaseMutex(&info->MediaChangeMutex, FALSE); + + return; +} // end DeviceSetMediaChangeStateEx() + + +_IRQL_requires_max_(APC_LEVEL) +VOID +DeviceSendDelayedMediaChangeNotifications( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + This routine sends the so-called delayed media change notifications. + These notifications get accumulated while the MCN mechanism is disabled + and need to be sent to the application on MCN enabling, if MCN enabling + happens as a part of exclusive access unlock and the application has not + requested us explicitly to not send the delayed notifications. + +Arguments: + + DeviceExtension - the device extension + +Return Value: + + none + +--*/ +{ + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + LARGE_INTEGER zero; + NTSTATUS status; + + PAGED_CODE(); + + zero.QuadPart = 0; + + if (info == NULL) + { + return; + } + + status = KeWaitForMutexObject(&info->MediaChangeMutex, + Executive, + KernelMode, + FALSE, + &zero); + + if (status == STATUS_TIMEOUT) + { + // Someone else is in the process of setting the media state. + // That's totally okay, we'll send delayed notifications later. + return; + } + + // If the last reported state and the last known state are different and + // MCN is enabled, generate a notification based on the last known state. + if ((info->LastKnownMediaDetectionState != info->LastReportedMediaDetectionState) && + (info->MediaChangeDetectionDisableCount == 0)) + { + DeviceInternalSetMediaChangeState(DeviceExtension, info->LastKnownMediaDetectionState, NULL); + } + + KeReleaseMutex(&info->MediaChangeMutex, FALSE); + + return; +} + + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +RequestSetupMcnRequest( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ BOOLEAN UseGesn +) +/*++ + +Routine Description: + + This routine sets up the fields of the request for MCN + +Arguments: + DeviceExtension - device context + + UseGesn - If TRUE, the device supports GESN and it's currently the mechanism for MCN + +Return Value: + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PSCSI_REQUEST_BLOCK srb; + PIRP irp; + PIO_STACK_LOCATION nextIrpStack; + PCDB cdb; + PVOID buffer; + WDF_REQUEST_REUSE_PARAMS params; + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + + PAGED_CODE(); + + irp = WdfRequestWdmGetIrp(info->MediaChangeRequest); + NT_ASSERT(irp != NULL); + + // deassign the MdlAddress, this is the value we assign explicitly. + // this is to prevent WdfRequestReuse to release the Mdl unexpectly. + if (irp->MdlAddress) + { + irp->MdlAddress = NULL; + } + + if (NT_SUCCESS(status)) + { + // Setup the IRP to perform a test unit ready. + WDF_REQUEST_REUSE_PARAMS_INIT(¶ms, + WDF_REQUEST_REUSE_NO_FLAGS, + STATUS_NOT_SUPPORTED); + + status = WdfRequestReuse(info->MediaChangeRequest, ¶ms); + } + + if (NT_SUCCESS(status)) + { + // Format the request. + status = WdfIoTargetFormatRequestForInternalIoctlOthers(DeviceExtension->IoTarget, + info->MediaChangeRequest, + IOCTL_SCSI_EXECUTE_IN, + NULL, NULL, + NULL, NULL, + NULL, NULL); + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "RequestSetupMcnRequest: WdfIoTargetFormatRequestForInternalIoctlOthers failed, %!STATUS!\n", + status)); + } + } + + if (NT_SUCCESS(status)) + { + RequestClearSendTime(info->MediaChangeRequest); + + nextIrpStack = IoGetNextIrpStackLocation(irp); + + nextIrpStack->Flags = SL_OVERRIDE_VERIFY_VOLUME; + nextIrpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + nextIrpStack->Parameters.Scsi.Srb = &(info->MediaChangeSrb); + + // Prepare the SRB for execution. + srb = nextIrpStack->Parameters.Scsi.Srb; + buffer = info->SenseBuffer; + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + RtlZeroMemory(buffer, SENSE_BUFFER_SIZE); + + srb->QueueTag = SP_UNTAGGED; + srb->QueueAction = SRB_SIMPLE_TAG_REQUEST; + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + srb->SenseInfoBuffer = buffer; + srb->SrbStatus = 0; + srb->ScsiStatus = 0; + srb->OriginalRequest = irp; + srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE; + + srb->SrbFlags = DeviceExtension->SrbFlags; + SET_FLAG(srb->SrbFlags, info->SrbFlags); + + if (!UseGesn) + { + srb->TimeOutValue = CDROM_TEST_UNIT_READY_TIMEOUT; + srb->CdbLength = 6; + srb->DataTransferLength = 0; + SET_FLAG(srb->SrbFlags, SRB_FLAGS_NO_DATA_TRANSFER); + nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_NONE; + srb->DataBuffer = NULL; + srb->DataTransferLength = 0; + irp->MdlAddress = NULL; + + cdb = (PCDB) &srb->Cdb[0]; + cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY; + } + else + { + NT_ASSERT(info->Gesn.Buffer); + + srb->TimeOutValue = GESN_TIMEOUT_VALUE; // much shorter timeout for GESN + + srb->CdbLength = 10; + SET_FLAG(srb->SrbFlags, SRB_FLAGS_DATA_IN); + nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_IN; + srb->DataBuffer = info->Gesn.Buffer; + srb->DataTransferLength = info->Gesn.BufferSize; + irp->MdlAddress = info->Gesn.Mdl; + + cdb = (PCDB) &srb->Cdb[0]; + cdb->GET_EVENT_STATUS_NOTIFICATION.OperationCode = SCSIOP_GET_EVENT_STATUS; + cdb->GET_EVENT_STATUS_NOTIFICATION.Immediate = 1; + cdb->GET_EVENT_STATUS_NOTIFICATION.EventListLength[0] = (UCHAR)((info->Gesn.BufferSize) >> 8); + cdb->GET_EVENT_STATUS_NOTIFICATION.EventListLength[1] = (UCHAR)((info->Gesn.BufferSize) & 0xff); + cdb->GET_EVENT_STATUS_NOTIFICATION.NotificationClassRequest = info->Gesn.EventMask; + } + } + + return status; +} + + +VOID +NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ +DeviceDisableGesn( + _In_ WDFWORKITEM WorkItem + ) +/*++ + +Routine Description: + + Work item routine to set the hack flag in the registry to disable GESN + This routine is invoked when the device reports TOO many events that affects system + +Arguments: + WorkItem - the work item be perfromed. + +Return Value: + None + +--*/ +{ + WDFDEVICE device = WdfWorkItemGetParentObject(WorkItem); + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device); + + PAGED_CODE(); + + // + // Set the hack flag in the registry + // + DeviceSetParameter(deviceExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_MMC_DETECTION_VALUE_NAME, + CdromDetectionUnsupported); + + WdfObjectDelete(WorkItem); + + return; +} + +_IRQL_requires_max_(APC_LEVEL) +BOOLEAN +RequestPostWorkMcnRequest( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + This routine handles the completion of the test unit ready irps used to + determine if the media has changed. If the media has changed, this code + signals the named event to wake up other system services that react to + media change (aka AutoPlay). + +Arguments: + + DeviceExtension - the device context + +Return Value: + + BOOLEAN - TRUE (needs retry); FALSE (shoule not retry) + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + PIRP irp; + BOOLEAN retryImmediately = FALSE; + + PAGED_CODE(); + + NT_ASSERT(info->MediaChangeRequest != NULL); + irp = WdfRequestWdmGetIrp(info->MediaChangeRequest); + + NT_ASSERT(!TEST_FLAG(info->MediaChangeSrb.SrbStatus, SRB_STATUS_QUEUE_FROZEN)); + + // use InterpretSenseInfo routine to check for media state, and also + // to call ClassError() with correct parameters. + if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_SUCCESS) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "MCN request - failed - srb status=%x, sense=%x/%x/%x.\n", + info->MediaChangeSrb.SrbStatus, + ((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->SenseKey, + ((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->AdditionalSenseCode, + ((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->AdditionalSenseCodeQualifier)); + + if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_NOT_POWERED) + { + // Release the queue if it is frozen. + if (info->MediaChangeSrb.SrbStatus & SRB_STATUS_QUEUE_FROZEN) + { + DeviceReleaseQueue(DeviceExtension->Device); + } + + RequestSenseInfoInterpret(DeviceExtension, + info->MediaChangeRequest, + &info->MediaChangeSrb, + 0, + &status, + NULL); + } + } + else + { + DeviceExtension->PrivateFdoData->LoggedTURFailureSinceLastIO = FALSE; + + if (!info->Gesn.Supported) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "MCN request - succeeded (GESN NOT supported, setting MediaPresent).\n")); + + // success != media for GESN case + DeviceSetMediaChangeStateEx(DeviceExtension, + MediaPresent, + NULL); + } + else + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, + "MCN request - succeeded (GESN supported).\n")); + } + } + + if (info->Gesn.Supported) + { + if (status == STATUS_DATA_OVERRUN) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request - Data Overrun\n")); + status = STATUS_SUCCESS; + } + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request: GESN failed with status %x\n", status)); + } + else + { + // for GESN, need to interpret the results of the data. + // this may also require an immediate retry + if (irp->IoStatus.Information == 8 ) + { + GesnDataInterpret(DeviceExtension, + (PVOID)info->Gesn.Buffer, + &retryImmediately); + } + + } // end of NT_SUCCESS(status) + + } // end of Info->Gesn.Supported + + // free port-allocated sense buffer, if any. + if (PORT_ALLOCATED_SENSE(DeviceExtension, &info->MediaChangeSrb)) + { + FREE_PORT_ALLOCATED_SENSE_BUFFER(DeviceExtension, &info->MediaChangeSrb); + } + + // Remember the IRP and SRB for use the next time. + NT_ASSERT(IoGetNextIrpStackLocation(irp)); + IoGetNextIrpStackLocation(irp)->Parameters.Scsi.Srb = &info->MediaChangeSrb; + + // run a sanity check to make sure we're not recursing continuously + if (retryImmediately) + { + info->MediaChangeRetryCount++; + + if (info->MediaChangeRetryCount > MAXIMUM_IMMEDIATE_MCN_RETRIES) + { + // Disable GESN on this device. + // Create a work item to set the value in the registry + WDF_OBJECT_ATTRIBUTES attributes; + WDF_WORKITEM_CONFIG workitemConfig; + WDFWORKITEM workItem; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = DeviceExtension->Device; + + WDF_WORKITEM_CONFIG_INIT(&workitemConfig, DeviceDisableGesn); + workitemConfig.AutomaticSerialization = FALSE; + + status = WdfWorkItemCreate(&workitemConfig, + &attributes, + &workItem); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request: Disabling GESN for WDFDEVICE %p\n", DeviceExtension->Device)); + + if (NT_SUCCESS(status)) + { + WdfWorkItemEnqueue(workItem); + } + + info->Gesn.Supported = FALSE; + info->Gesn.EventMask = 0; + info->Gesn.BufferSize = 0; + info->MediaChangeRetryCount = 0; + retryImmediately = FALSE; + // should we log an error in event log? + } + } + else + { + info->MediaChangeRetryCount = 0; + } + + return retryImmediately; +} + + +_IRQL_requires_max_(APC_LEVEL) +BOOLEAN +RequestSendMcnRequest( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + This routine sends the formatted MCN request sychronizely to lower driver. + +Arguments: + + DeviceExtension - the device context + +Return Value: + BOOLEAN - TRUE (requst successfully sent); FALSE (request failed to send) + +--*/ +{ + BOOLEAN requestSent = FALSE; + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + + PAGED_CODE(); + + RequestSend(DeviceExtension, + info->MediaChangeRequest, + DeviceExtension->IoTarget, + WDF_REQUEST_SEND_OPTION_SYNCHRONOUS, + &requestSent); + + return requestSent; +} // end RequestSendMcnRequest() + + + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceInitializeMcn( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ BOOLEAN AllowDriveToSleep + ) +/*++ + +Routine Description: + + This routine initialize the contents of MCN structure. + +Arguments: + + DeviceExtension - the device extension + + AllowDriveToSleep - for CDROM, this parameter should be always FALSE + +Return Value: + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PMEDIA_CHANGE_DETECTION_INFO mediaChangeInfo = NULL; + PIRP irp = NULL; + PVOID senseBuffer = NULL; + WDF_OBJECT_ATTRIBUTES attributes; + + PAGED_CODE(); + + if (DeviceExtension->MediaChangeDetectionInfo != NULL) + { + //Already initialized. + return STATUS_SUCCESS; + } + + DeviceExtension->KernelModeMcnContext.FileObject = (PVOID)-1; + DeviceExtension->KernelModeMcnContext.DeviceObject = (PVOID)-1; + DeviceExtension->KernelModeMcnContext.LockCount = 0; + DeviceExtension->KernelModeMcnContext.McnDisableCount = 0; + + mediaChangeInfo = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(MEDIA_CHANGE_DETECTION_INFO), + CDROM_TAG_MEDIA_CHANGE_DETECTION); + + if (mediaChangeInfo == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + else + { + RtlZeroMemory(mediaChangeInfo, sizeof(MEDIA_CHANGE_DETECTION_INFO)); + } + + if (NT_SUCCESS(status)) + { + if ((DeviceExtension->PowerDescriptor != NULL) && + (DeviceExtension->PowerDescriptor->AsynchronousNotificationSupported != FALSE) && + (!TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_ASYNCHRONOUS_NOTIFICATION))) + { + mediaChangeInfo->AsynchronousNotificationSupported = TRUE; + } + } + + // Allocate an IRP to carry the IOCTL_MCN_SYNC_FAKE_IOCTL. + if (NT_SUCCESS(status)) + { + irp = IoAllocateIrp(DeviceExtension->DeviceObject->StackSize, FALSE); + + if (irp == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + if (NT_SUCCESS(status)) + { + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, + CDROM_REQUEST_CONTEXT); + attributes.ParentObject = DeviceExtension->Device; + status = WdfRequestCreate(&attributes, + DeviceExtension->IoTarget, + &mediaChangeInfo->MediaChangeRequest); + } + + if (NT_SUCCESS(status)) + { + // Preformat the media change request. With this being done, we never need to worry about + // WdfIoTargetFormatRequestForInternalIoctlOthers ever failing later. + status = WdfIoTargetFormatRequestForInternalIoctlOthers(DeviceExtension->IoTarget, + mediaChangeInfo->MediaChangeRequest, + IOCTL_SCSI_EXECUTE_IN, + NULL, NULL, + NULL, NULL, + NULL, NULL); + } + + if (NT_SUCCESS(status)) + { + senseBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, + SENSE_BUFFER_SIZE, + CDROM_TAG_MEDIA_CHANGE_DETECTION); + if (senseBuffer == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + if (NT_SUCCESS(status)) + { + mediaChangeInfo->MediaChangeSyncIrp = irp; + mediaChangeInfo->SenseBuffer = senseBuffer; + + // Set default values for the media change notification + // configuration. + mediaChangeInfo->MediaChangeDetectionDisableCount = 0; + + // Assume that there is initially no media in the device + // only notify upper layers if there is something there + mediaChangeInfo->LastKnownMediaDetectionState = MediaUnknown; + mediaChangeInfo->LastReportedMediaDetectionState = MediaUnknown; + + // setup all extra flags we'll be setting for this irp + mediaChangeInfo->SrbFlags = 0; + + SET_FLAG(mediaChangeInfo->SrbFlags, SRB_CLASS_FLAGS_LOW_PRIORITY); + SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE); + SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER); + + if (AllowDriveToSleep) //FALSE for CD/DVD devices + { + SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_NO_KEEP_AWAKE); + } + + KeInitializeMutex(&mediaChangeInfo->MediaChangeMutex, 0x100); + + // It is ok to support media change events on this device. + DeviceExtension->MediaChangeDetectionInfo = mediaChangeInfo; + + // check the device supports GESN or not, initialize GESN structure if it supports. + { + // This is only valid for type5 devices. + NTSTATUS tempStatus = STATUS_SUCCESS; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMcn: Testing for GESN\n")); + tempStatus = DeviceInitializeGesn(DeviceExtension); + + if (NT_SUCCESS(tempStatus)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMcn: GESN available for %p\n", + DeviceExtension->DeviceObject)); + NT_ASSERT(mediaChangeInfo->Gesn.Supported ); + NT_ASSERT(mediaChangeInfo->Gesn.Buffer != NULL); + NT_ASSERT(mediaChangeInfo->Gesn.BufferSize != 0); + NT_ASSERT(mediaChangeInfo->Gesn.EventMask != 0); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMcn: GESN *NOT* available for %p\n", + DeviceExtension->DeviceObject)); + NT_ASSERT(!mediaChangeInfo->Gesn.Supported); + NT_ASSERT(mediaChangeInfo->Gesn.Buffer == NULL); + NT_ASSERT(mediaChangeInfo->Gesn.BufferSize == 0); + NT_ASSERT(mediaChangeInfo->Gesn.EventMask == 0); + mediaChangeInfo->Gesn.Supported = FALSE; // just in case.... + } + } + } + + if (NT_SUCCESS(status)) + { + // Register for display state change on AOAC capable systems so we can put the + // device to low power state when not required. + if (mediaChangeInfo->DisplayStateCallbackHandle == NULL) + { + POWER_PLATFORM_INFORMATION PlatformInfo = {0}; + + status = ZwPowerInformation(PlatformInformation, + NULL, + 0, + &PlatformInfo, + sizeof(PlatformInfo)); + + if (NT_SUCCESS(status) && PlatformInfo.AoAc) + { + PoRegisterPowerSettingCallback(DeviceExtension->DeviceObject, + &GUID_CONSOLE_DISPLAY_STATE, + &DevicePowerSettingCallback, + DeviceExtension, + &mediaChangeInfo->DisplayStateCallbackHandle); + } + + // Ignore any failures above. + status = STATUS_SUCCESS; + } + } + + if (!NT_SUCCESS(status)) + { + if (irp != NULL) + { + IoFreeIrp(irp); + } + FREE_POOL(senseBuffer); + FREE_POOL(mediaChangeInfo); + } + + return status; + +} // end DeviceInitializeMcn() + + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceInitializeGesn( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + This routine initialize the contents of GESN structure. + +Arguments: + + DeviceExtension - the device extension + +Return Value: + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PNOTIFICATION_EVENT_STATUS_HEADER header = NULL; + CDROM_DETECTION_STATE detectionState = CdromDetectionUnknown; + PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = DeviceExtension->DeviceDescriptor; + BOOLEAN retryImmediately = TRUE; + ULONG i = 0; + ULONG atapiResets = 0; + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + + PAGED_CODE(); + + NT_ASSERT(info != NULL); + + // read if we already know the abilities of the device + DeviceGetParameter(DeviceExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_MMC_DETECTION_VALUE_NAME, + (PULONG)&detectionState); + + if (detectionState == CdromDetectionUnsupported) + { + status = STATUS_NOT_SUPPORTED; + } + + // check if the device has a hack flag saying never to try this. + if (NT_SUCCESS(status) && + (TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_GESN_IS_BAD)) ) + { + DeviceSetParameter(DeviceExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_MMC_DETECTION_VALUE_NAME, + CdromDetectionUnsupported); + status = STATUS_NOT_SUPPORTED; + } + + // else go through the process since we allocate buffers and + // get all sorts of device settings. + if (NT_SUCCESS(status)) + { + if (info->Gesn.Buffer == NULL) + { + info->Gesn.Buffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, + GESN_BUFFER_SIZE, + CDROM_TAG_GESN); + } + + if (info->Gesn.Buffer == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + if (NT_SUCCESS(status)) + { + if (info->Gesn.Mdl != NULL) + { + IoFreeMdl(info->Gesn.Mdl); + } + + info->Gesn.Mdl = IoAllocateMdl(info->Gesn.Buffer, + GESN_BUFFER_SIZE, + FALSE, + FALSE, + NULL); + if (info->Gesn.Mdl == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + if (NT_SUCCESS(status)) + { + MmBuildMdlForNonPagedPool(info->Gesn.Mdl); + info->Gesn.BufferSize = GESN_BUFFER_SIZE; + info->Gesn.EventMask = 0; + + // all items are prepared to use GESN (except the event mask, so don't + // optimize this part out!). + // + // now see if it really works. we have to loop through this because + // many SAMSUNG (and one COMPAQ) drives timeout when requesting + // NOT_READY events, even when the IMMEDIATE bit is set. :( + // + // using a drive list is cumbersome, so this might fix the problem. + for (i = 0; (i < 16) && retryImmediately; i++) + { + status = RequestSetupMcnRequest(DeviceExtension, TRUE); + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "Setup Mcn request failed %x for WDFDEVICE %p\n", + status, DeviceExtension->Device)); + break; + } + + NT_ASSERT(TEST_FLAG(info->MediaChangeSrb.SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE)); + + status = DeviceSendRequestSynchronously(DeviceExtension->Device, info->MediaChangeRequest, TRUE); + + if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_SUCCESS) + { + // Release the queue if it is frozen. + if (info->MediaChangeSrb.SrbStatus & SRB_STATUS_QUEUE_FROZEN) + { + DeviceReleaseQueue(DeviceExtension->Device); + } + + RequestSenseInfoInterpret(DeviceExtension, + info->MediaChangeRequest, + &(info->MediaChangeSrb), + 0, + &status, + NULL); + } + + if ((deviceDescriptor->BusType == BusTypeAtapi) && + (info->MediaChangeSrb.SrbStatus == SRB_STATUS_BUS_RESET)) + { + // + // ATAPI unfortunately returns SRB_STATUS_BUS_RESET instead + // of SRB_STATUS_TIMEOUT, so we cannot differentiate between + // the two. if we get this status four time consecutively, + // stop trying this command. it is too late to change ATAPI + // at this point, so special-case this here. (07/10/2001) + // NOTE: any value more than 4 may cause the device to be + // marked missing. + // + atapiResets++; + if (atapiResets >= 4) + { + status = STATUS_IO_DEVICE_ERROR; + break; + } + } + + if (status == STATUS_DATA_OVERRUN) + { + status = STATUS_SUCCESS; + } + + if ((status == STATUS_INVALID_DEVICE_REQUEST) || + (status == STATUS_TIMEOUT) || + (status == STATUS_IO_DEVICE_ERROR) || + (status == STATUS_IO_TIMEOUT)) + { + // with these error codes, we don't ever want to try this command + // again on this device, since it reacts poorly. + DeviceSetParameter( DeviceExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_MMC_DETECTION_VALUE_NAME, + CdromDetectionUnsupported); + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "GESN test failed %x for WDFDEVICE %p\n", + status, DeviceExtension->Device)); + break; + } + + if (!NT_SUCCESS(status)) + { + // this may be other errors that should not disable GESN + // for all future start_device calls. + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "GESN test failed %x for WDFDEVICE %p\n", + status, DeviceExtension->Device)); + break; + } + else if (i == 0) + { + // the first time, the request was just retrieving a mask of + // available bits. use this to mask future requests. + header = (PNOTIFICATION_EVENT_STATUS_HEADER)(info->Gesn.Buffer); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "WDFDEVICE %p supports event mask %x\n", + DeviceExtension->Device, header->SupportedEventClasses)); + + if (TEST_FLAG(header->SupportedEventClasses, + NOTIFICATION_MEDIA_STATUS_CLASS_MASK)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN supports MCN\n")); + } + if (TEST_FLAG(header->SupportedEventClasses, + NOTIFICATION_DEVICE_BUSY_CLASS_MASK)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN supports DeviceBusy\n")); + } + if (TEST_FLAG(header->SupportedEventClasses, + NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK)) + { + if (TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, + FDO_HACK_GESN_IGNORE_OPCHANGE)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN supports OpChange, but must ignore these events for compatibility\n")); + CLEAR_FLAG(header->SupportedEventClasses, + NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN supports OpChange\n")); + } + } + info->Gesn.EventMask = header->SupportedEventClasses; + + // + // realistically, we are only considering the following events: + // EXTERNAL REQUEST - this is being tested for play/stop/etc. + // MEDIA STATUS - autorun and ejection requests. + // DEVICE BUSY - to allow us to predict when media will be ready. + // therefore, we should not bother querying for the other, + // unknown events. clear all but the above flags. + // + info->Gesn.EventMask &= NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK | + NOTIFICATION_EXTERNAL_REQUEST_CLASS_MASK | + NOTIFICATION_MEDIA_STATUS_CLASS_MASK | + NOTIFICATION_DEVICE_BUSY_CLASS_MASK ; + + + // + // HACKHACK - REF #0001 + // Some devices will *never* report an event if we've also requested + // that it report lower-priority events. this is due to a + // misunderstanding in the specification wherein a "No Change" is + // interpreted to be a real event. what should occur is that the + // device should ignore "No Change" events when multiple event types + // are requested unless there are no other events waiting. this + // greatly reduces the number of requests that the host must send + // to determine if an event has occurred. Since we must work on all + // drives, default to enabling the hack until we find evidence of + // proper firmware. + // + if (info->Gesn.EventMask == 0) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN supported, but not mask we care about (%x) for FDO %p\n", + header->SupportedEventClasses, + DeviceExtension->DeviceObject)); + // NOTE: the status is still status_sucess. + break; + } + else if (CountOfSetBitsUChar(info->Gesn.EventMask) == 1) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN hack not required for FDO %p\n", + DeviceExtension->DeviceObject)); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN hack enabled for FDO %p\n", + DeviceExtension->DeviceObject)); + info->Gesn.HackEventMask = 1; + } + } + else + { + // i > 0; not the first time looping through, so interpret the results. + status = GesnDataInterpret(DeviceExtension, + (PVOID)info->Gesn.Buffer, + &retryImmediately); + + if (!NT_SUCCESS(status)) + { + // This drive does not support GESN correctly + DeviceSetParameter( DeviceExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_MMC_DETECTION_VALUE_NAME, + CdromDetectionUnsupported); + break; + } + } + } // end 'for' loop of GESN requests.... + } + + if (NT_SUCCESS(status)) + { + // + // we can only use this if it can be relied upon for media changes, + // since we are (by definition) no longer going to be polling via + // a TEST_UNIT_READY irp, and drives will not report UNIT ATTENTION + // for this command (although a filter driver, such as one for burning + // cd's, might still fake those errors). + // + // since we also rely upon NOT_READY events to change the cursor + // into a "wait" cursor; GESN is still more reliable than other + // methods, and includes eject button requests, so we'll use it + // without DEVICE_BUSY in Windows Vista. + // + + if (TEST_FLAG(info->Gesn.EventMask, NOTIFICATION_MEDIA_STATUS_CLASS_MASK)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "Enabling GESN support for WDFDEVICE %p\n", + DeviceExtension->Device)); + info->Gesn.Supported = TRUE; + + DeviceSetParameter( DeviceExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_MMC_DETECTION_VALUE_NAME, + CdromDetectionSupported); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "GESN available but not enabled for WDFDEVICE %p\n", + DeviceExtension->Device)); + status = STATUS_NOT_SUPPORTED; + } + } + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "GESN support detection failed for WDFDEVICE %p with status %08x\n", + DeviceExtension->Device, status)); + + if (info->Gesn.Mdl) + { + PIRP irp = WdfRequestWdmGetIrp(info->MediaChangeRequest); + + IoFreeMdl(info->Gesn.Mdl); + info->Gesn.Mdl = NULL; + irp->MdlAddress = NULL; + } + + FREE_POOL(info->Gesn.Buffer); + info->Gesn.Supported = FALSE; + info->Gesn.EventMask = 0; + info->Gesn.BufferSize = 0; + } + + return status; +} + + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceInitializeMediaChangeDetection( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + This routine checks to see if it is safe to initialize MCN (the back end + to autorun) for a given device. It will then check the device-type wide + key "Autorun" in the service key (for legacy reasons), and then look in + the device-specific key to potentially override that setting. + + If MCN is to be enabled, all neccessary structures and memory are + allocated and initialized. + + This routine MUST be called only from the DeviceInit...() . + +Arguments: + + DeviceExtension - the device to initialize MCN for, if appropriate + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + BOOLEAN disabled = FALSE; + BOOLEAN instanceOverride; + + PAGED_CODE(); + + // NOTE: This assumes that DeviceInitializeMediaChangeDetection is always + // called in the context of the DeviceInitDevicePhase2. If called + // after then this check will have already been made and the + // once a second timer will not have been enabled. + + disabled = DeviceIsMediaChangeDisabledDueToHardwareLimitation(DeviceExtension); + + if (disabled) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMediaChangeDetection: Disabled due to hardware" + "limitations for this device\n")); + } + else + { + // autorun should now be enabled by default for all media types. + disabled = DeviceIsMediaChangeDisabledForClass(DeviceExtension); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMediaChangeDetection: MCN is %s\n", + (disabled ? "disabled" : "enabled"))); + + status = DeviceMediaChangeDeviceInstanceOverride(DeviceExtension, + &instanceOverride); // default value + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMediaChangeDetection: Instance using default\n")); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMediaChangeDetection: Instance override: %s MCN\n", + (instanceOverride ? "Enabling" : "Disabling"))); + disabled = !instanceOverride; + } + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceInitializeMediaChangeDetection: Instance MCN is %s\n", + (disabled ? "disabled" : "enabled"))); + } + + if (!disabled) + { + // if the drive is not a CDROM, allow the drive to sleep + status = DeviceInitializeMcn(DeviceExtension, FALSE); + } + + return status; +} // end DeviceInitializeMediaChangeDetection() + + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceMediaChangeDeviceInstanceOverride( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _Out_ PBOOLEAN Enabled + ) +/*++ + +Routine Description: + + The user can override the global setting to enable or disable Autorun on a + specific cdrom device via the control panel. This routine checks and/or + sets this value. + +Arguments: + + DeviceExtension - the device to set/get the value for + + Enabled - TRUE (Autorun is enabled) + FALSE (Autorun is disabled) +Return Value: + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_UNSUCCESSFUL; + WDFKEY deviceKey = NULL; + WDFKEY subKey = NULL; + + UNICODE_STRING subkeyName; + UNICODE_STRING enableMcnValueName; + UNICODE_STRING disableMcnValueName; + ULONG alwaysEnable = 0; + ULONG alwaysDisable = 0; + + PAGED_CODE(); + + status = WdfDeviceOpenRegistryKey(DeviceExtension->Device, + PLUGPLAY_REGKEY_DEVICE, + KEY_ALL_ACCESS, + WDF_NO_OBJECT_ATTRIBUTES, + &deviceKey); + if (!NT_SUCCESS(status)) + { + // this can occur when a new device is added to the system + // this is due to cdrom.sys being an 'essential' driver + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceMediaChangeDeviceInstanceOverride: " + "Could not open device registry key [%lx]\n", status)); + } + else + { + RtlInitUnicodeString(&subkeyName, MCN_REG_SUBKEY_NAME); + + status = WdfRegistryOpenKey(deviceKey, + &subkeyName, + KEY_READ, + WDF_NO_OBJECT_ATTRIBUTES, + &subKey); + } + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceMediaChangeDeviceInstanceOverride: " + "subkey could not be created. %lx\n", status)); + } + else + { + // Default to not changing autorun behavior, based upon setting + // registryValue to zero. + RtlInitUnicodeString(&enableMcnValueName, MCN_REG_AUTORUN_ENABLE_INSTANCE_NAME); + RtlInitUnicodeString(&disableMcnValueName, MCN_REG_AUTORUN_DISABLE_INSTANCE_NAME); + + // Ignore failures on reading of subkeys + (VOID) WdfRegistryQueryULong(subKey, + &enableMcnValueName, + &alwaysEnable); + (VOID) WdfRegistryQueryULong(subKey, + &disableMcnValueName, + &alwaysDisable); + } + + // set return value and cleanup + + if (subKey != NULL) + { + WdfRegistryClose(subKey); + } + + if (deviceKey != NULL) + { + WdfRegistryClose(deviceKey); + } + + if (alwaysEnable && alwaysDisable) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceMediaChangeDeviceInstanceOverride: %s selected\n", + "Both Enable and Disable set -- DISABLE")); + NT_ASSERT(NT_SUCCESS(status)); + status = STATUS_SUCCESS; + *Enabled = FALSE; + } + else if (alwaysDisable) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceMediaChangeDeviceInstanceOverride: %s selected\n", + "DISABLE")); + NT_ASSERT(NT_SUCCESS(status)); + status = STATUS_SUCCESS; + *Enabled = FALSE; + } + else if (alwaysEnable) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceMediaChangeDeviceInstanceOverride: %s selected\n", + "ENABLE")); + NT_ASSERT(NT_SUCCESS(status)); + status = STATUS_SUCCESS; + *Enabled = TRUE; + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceMediaChangeDeviceInstanceOverride: %s selected\n", + "DEFAULT")); + status = STATUS_UNSUCCESSFUL; + } + + return status; +} // end DeviceMediaChangeDeviceInstanceOverride() + + +NTSTATUS +NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ +DeviceMediaChangeRegistryCallBack( + _In_z_ PWSTR ValueName, + _In_ ULONG ValueType, + _In_reads_bytes_opt_(ValueLength) PVOID ValueData, + _In_ ULONG ValueLength, + _In_opt_ PVOID Context, + _In_opt_ PVOID EntryContext + ) +/*++ + +Routine Description: + + This callback for a registry SZ or MULTI_SZ is called once for each + SZ in the value. It will attempt to match the data with the + UNICODE_STRING passed in as Context, and modify EntryContext if a + match is found. Written for ClasspCheckRegistryForMediaChangeCompletion + +Arguments: + + ValueName - name of the key that was opened + ValueType - type of data stored in the value (REG_SZ for this routine) + ValueData - data in the registry, in this case a wide string + ValueLength - length of the data including the terminating null + Context - unicode string to compare against ValueData + EntryContext - should be initialized to 0, will be set to 1 if match found + +Return Value: + + STATUS_SUCCESS + EntryContext will be 1 if found + +--*/ +{ + PULONG valueFound; + PUNICODE_STRING deviceString; + PWSTR keyValue; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ValueName); + + if ((Context == NULL) || (EntryContext == NULL)) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "DeviceMediaChangeRegistryCallBack: NULL context should never be passed to registry call-back!\n")); + + return STATUS_SUCCESS; + } + + // if we have already set the value to true, exit + valueFound = EntryContext; + if ((*valueFound) != 0) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceMediaChangeRegistryCallBack: already set to true\n")); + return STATUS_SUCCESS; + } + + if (ValueLength == sizeof(WCHAR)) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "DeviceMediaChangeRegistryCallBack: NULL string should never be passed to registry call-back!\n")); + return STATUS_SUCCESS; + } + + // if the data is not a terminated string, exit + if (ValueType != REG_SZ) + { + return STATUS_SUCCESS; + } + + deviceString = Context; + keyValue = ValueData; + ValueLength -= sizeof(WCHAR); // ignore the null character + + // do not compare more memory than is in deviceString + if (ValueLength > deviceString->Length) + { + ValueLength = deviceString->Length; + } + + if (keyValue == NULL) + { + return STATUS_SUCCESS; + } + + // if the strings match, disable autorun + if (RtlCompareMemory(deviceString->Buffer, keyValue, ValueLength) == ValueLength) + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "DeviceMediaChangeRegistryCallBack: Match found\n")); + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "DeviceMediaChangeRegistryCallBack: DeviceString at %p\n", + deviceString->Buffer)); + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, + "DeviceMediaChangeRegistryCallBack: KeyValue at %p\n", + keyValue)); + (*valueFound) = TRUE; + } + + return STATUS_SUCCESS; +} // end DeviceMediaChangeRegistryCallBack() + + +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +DeviceIsMediaChangeDisabledDueToHardwareLimitation( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + The key AutoRunAlwaysDisable contains a MULTI_SZ of hardware IDs for + which to never enable MediaChangeNotification. + + The user can override the global setting to enable or disable Autorun on a + specific cdrom device via the control panel. + + NOTE: It's intended that not use WdfRegistryQueryMultiString in this funciton, + as it's much more complicated.(needs WDFCOLLECTION, WDFSTRING other than + UNICODE_STRING) + +Arguments: + + FdoExtension - + RegistryPath - pointer to the unicode string inside + ...\CurrentControlSet\Services\Cdrom + +Return Value: + + TRUE - no autorun. + FALSE - Autorun may be enabled + +--*/ +{ + NTSTATUS status; + + PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = DeviceExtension->DeviceDescriptor; + WDFKEY wdfKey; + HANDLE serviceKey = NULL; + RTL_QUERY_REGISTRY_TABLE parameters[2] = {0}; + + UNICODE_STRING deviceUnicodeString = {0}; + ANSI_STRING deviceString = {0}; + ULONG mediaChangeNotificationDisabled = 0; + + PAGED_CODE(); + + // open the service key. + status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(), + KEY_ALL_ACCESS, + WDF_NO_OBJECT_ATTRIBUTES, + &wdfKey); + + if(!NT_SUCCESS(status)) + { + // NT_ASSERT(FALSE); __REACTOS__ : allow to fail (for 1st stage setup) + + // always take the safe path. if we can't open the service key, disable autorun + return TRUE; + } + + if(NT_SUCCESS(status)) + { + // Determine if drive is in a list of those requiring + // autorun to be disabled. this is stored in a REG_MULTI_SZ + // named AutoRunAlwaysDisable. this is required as some autochangers + // must load the disc to reply to ChkVerify request, causing them + // to cycle discs continuously. + + PWSTR nullMultiSz; + PUCHAR vendorId = NULL; + PUCHAR productId = NULL; + PUCHAR revisionId = NULL; + size_t length; + size_t offset; + + deviceString.Buffer = NULL; + deviceUnicodeString.Buffer = NULL; + + serviceKey = WdfRegistryWdmGetHandle(wdfKey); + + // there may be nothing to check against + if ((deviceDescriptor->VendorIdOffset == 0) && + (deviceDescriptor->ProductIdOffset == 0)) + { + // no valid data in device extension. + status = STATUS_INTERNAL_ERROR; + } + + // build deviceString using VendorId, Model and Revision. + // this string will be used to checked if it's one of devices in registry disable list. + if (NT_SUCCESS(status)) + { + length = 0; + + if (deviceDescriptor->VendorIdOffset == 0) + { + vendorId = NULL; + } + else + { + vendorId = (PUCHAR) deviceDescriptor + deviceDescriptor->VendorIdOffset; + length = strlen((LPCSTR)vendorId); + } + + if ( deviceDescriptor->ProductIdOffset == 0 ) + { + productId = NULL; + } + else + { + productId = (PUCHAR) deviceDescriptor + deviceDescriptor->ProductIdOffset; + length += strlen((LPCSTR)productId); + } + + if ( deviceDescriptor->ProductRevisionOffset == 0 ) + { + revisionId = NULL; + } + else + { + revisionId = (PUCHAR) deviceDescriptor + deviceDescriptor->ProductRevisionOffset; + length += strlen((LPCSTR)revisionId); + } + + // allocate a buffer for the string + deviceString.Length = (USHORT)( length ); + deviceString.MaximumLength = deviceString.Length + 1; + deviceString.Buffer = (PCHAR)ExAllocatePoolWithTag( NonPagedPoolNx, + deviceString.MaximumLength, + CDROM_TAG_AUTORUN_DISABLE + ); + if (deviceString.Buffer == NULL) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceIsMediaChangeDisabledDueToHardwareLimitation: Unable to alloc string buffer\n" )); + status = STATUS_INTERNAL_ERROR; + } + } + + if (NT_SUCCESS(status)) + { + // copy strings to the buffer + offset = 0; + + if (vendorId != NULL) + { + RtlCopyMemory(deviceString.Buffer + offset, + vendorId, + strlen((LPCSTR)vendorId)); + offset += strlen((LPCSTR)vendorId); + } + + if ( productId != NULL ) + { + RtlCopyMemory(deviceString.Buffer + offset, + productId, + strlen((LPCSTR)productId)); + offset += strlen((LPCSTR)productId); + } + if ( revisionId != NULL ) + { + RtlCopyMemory(deviceString.Buffer + offset, + revisionId, + strlen((LPCSTR)revisionId)); + offset += strlen((LPCSTR)revisionId); + } + + NT_ASSERT(offset == deviceString.Length); + + #pragma warning(suppress:6386) // Not an issue as deviceString.Buffer is of size deviceString.MaximumLength, which is equal to (deviceString.Length + 1) + deviceString.Buffer[deviceString.Length] = '\0'; // Null-terminated + + // convert to unicode as registry deals with unicode strings + status = RtlAnsiStringToUnicodeString( &deviceUnicodeString, + &deviceString, + TRUE + ); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceIsMediaChangeDisabledDueToHardwareLimitation: cannot convert " + "to unicode %lx\n", status)); + } + } + + if (NT_SUCCESS(status)) + { + // query the value, setting valueFound to true if found + nullMultiSz = L"\0"; + parameters[0].QueryRoutine = DeviceMediaChangeRegistryCallBack; + parameters[0].Flags = RTL_QUERY_REGISTRY_REQUIRED; + parameters[0].Name = L"AutoRunAlwaysDisable"; + parameters[0].EntryContext = &mediaChangeNotificationDisabled; + parameters[0].DefaultType = REG_MULTI_SZ; + parameters[0].DefaultData = nullMultiSz; + parameters[0].DefaultLength = 0; + + status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE, + serviceKey, + parameters, + &deviceUnicodeString, + NULL); + UNREFERENCED_PARAMETER(status); //defensive coding, avoid PREFAST warning. + } + } + + // Cleanup + { + + FREE_POOL( deviceString.Buffer ); + if (deviceUnicodeString.Buffer != NULL) + { + RtlFreeUnicodeString( &deviceUnicodeString ); + } + + // handle serviceKey will be closed by framework while it closes registry key. + WdfRegistryClose(wdfKey); + } + + if (mediaChangeNotificationDisabled > 0) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceIsMediaChangeDisabledDueToHardwareLimitation: Device is on MCN disable list\n")); + } + + return (mediaChangeNotificationDisabled > 0); + +} // end DeviceIsMediaChangeDisabledDueToHardwareLimitation() + + +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +DeviceIsMediaChangeDisabledForClass( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + The user must specify that AutoPlay is to run on the platform + by setting the registry value HKEY_LOCAL_MACHINE\System\CurrentControlSet\ + Services<SERVICE>\Autorun:REG_DWORD:1. + + The user can override the global setting to enable or disable Autorun on a + specific cdrom device via the control panel. + +Arguments: + + DeviceExtension - device extension + +Return Value: + + TRUE - Autorun is disabled for this class + FALSE - Autorun is enabled for this class + +--*/ +{ + NTSTATUS status; + WDFKEY serviceKey = NULL; + WDFKEY parametersKey = NULL; + + UNICODE_STRING parameterKeyName; + UNICODE_STRING valueName; + + // Default to ENABLING MediaChangeNotification (!) + ULONG mcnRegistryValue = 1; + + PAGED_CODE(); + + // open the service key. + status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(), + KEY_ALL_ACCESS, + WDF_NO_OBJECT_ATTRIBUTES, + &serviceKey); + if(!NT_SUCCESS(status)) + { + // return the default value, which is the inverse of the registry setting default + // since this routine asks if it's disabled + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceIsMediaChangeDisabledForClass: Defaulting to %s\n", + (mcnRegistryValue ? "Enabled" : "Disabled"))); + return (BOOLEAN)(mcnRegistryValue == 0); + } + else + { + // Open the parameters key (if any) beneath the services key. + RtlInitUnicodeString(¶meterKeyName, L"Parameters"); + + status = WdfRegistryOpenKey(serviceKey, + ¶meterKeyName, + KEY_READ, + WDF_NO_OBJECT_ATTRIBUTES, + ¶metersKey); + } + + if (!NT_SUCCESS(status)) + { + parametersKey = NULL; + } + + RtlInitUnicodeString(&valueName, L"Autorun"); + // ignore failures + status = WdfRegistryQueryULong(serviceKey, + &valueName, + &mcnRegistryValue); + + if (NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, + "DeviceIsMediaChangeDisabledForClass: <Service>/Autorun flag = %d\n", + mcnRegistryValue)); + } + + if (parametersKey != NULL) + { + status = WdfRegistryQueryULong(parametersKey, + &valueName, + &mcnRegistryValue); + + if (NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, + "DeviceIsMediaChangeDisabledForClass: <Service>/Parameters/Autorun flag = %d\n", + mcnRegistryValue)); + } + + WdfRegistryClose(parametersKey); + } + + WdfRegistryClose(serviceKey); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceIsMediaChangeDisabledForClass: Autoplay for device %p is %s\n", + DeviceExtension->DeviceObject, + (mcnRegistryValue ? "on" : "off") + )); + + // return if it is _disabled_, which is the + // inverse of the registry setting + + return (BOOLEAN)(!mcnRegistryValue); +} // end DeviceIsMediaChangeDisabledForClass() + + +_IRQL_requires_max_(APC_LEVEL) +VOID +DeviceEnableMediaChangeDetection( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _Inout_ PFILE_OBJECT_CONTEXT FileObjectContext, + _In_ BOOLEAN IgnorePreviousMediaChanges + ) +/*++ + +Routine Description: + + When the disable count decrease to 0, enable the MCN + +Arguments: + + DeviceExtension - the device context + + FileObjectContext - the file object context + + IgnorePreviousMediaChanges - ignore all previous media changes + +Return Value: + None. + +--*/ +{ + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + LONG oldCount; + + PAGED_CODE(); + + if (FileObjectContext) + { + InterlockedDecrement((PLONG)&(FileObjectContext->McnDisableCount)); + } + + if (info == NULL) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceEnableMediaChangeDetection: not initialized\n")); + return; + } + + (VOID) KeWaitForMutexObject(&info->MediaChangeMutex, + UserRequest, + KernelMode, + FALSE, + NULL); + + oldCount = --info->MediaChangeDetectionDisableCount; + + NT_ASSERT(oldCount >= 0); + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, + "DeviceEnableMediaChangeDetection: Disable count reduced to %d - \n", + info->MediaChangeDetectionDisableCount)); + + if (oldCount == 0) + { + if (IgnorePreviousMediaChanges) + { + info->LastReportedMediaDetectionState = info->LastKnownMediaDetectionState; + } + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN is enabled\n")); + } + else + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "MCD still disabled\n")); + } + + // Let something else run. + KeReleaseMutex(&info->MediaChangeMutex, FALSE); + + return; +} // end DeviceEnableMediaChangeDetection() + + +_IRQL_requires_max_(APC_LEVEL) +VOID +DeviceDisableMediaChangeDetection( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _Inout_ PFILE_OBJECT_CONTEXT FileObjectContext + ) +/*++ + +Routine Description: + + Increase the disable count. + +Arguments: + + DeviceExtension - the device context + + FileObjectContext - the file object context + +Return Value: + None. + +--*/ +{ + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + + PAGED_CODE(); + + if (FileObjectContext) + { + InterlockedIncrement((PLONG)&(FileObjectContext->McnDisableCount)); + } + + if (info == NULL) + { + return; + } + + (VOID) KeWaitForMutexObject(&info->MediaChangeMutex, + UserRequest, + KernelMode, + FALSE, + NULL); + + info->MediaChangeDetectionDisableCount++; + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, + "DisableMediaChangeDetection: disable count is %d\n", + info->MediaChangeDetectionDisableCount)); + + KeReleaseMutex(&info->MediaChangeMutex, FALSE); + + return; +} // end DeviceDisableMediaChangeDetection() + + +_IRQL_requires_max_(APC_LEVEL) +VOID +DeviceReleaseMcnResources( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + This routine will cleanup any resources allocated for MCN. It is called + by classpnp during remove device, and therefore is not typically required + by external drivers. + +Arguments: + + DeviceExtension - the device context + +Return Value: + None. + +--*/ +{ + PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo; + + PAGED_CODE() + + if(info == NULL) + { + return; + } + + if (info->Gesn.Mdl) + { + PIRP irp = WdfRequestWdmGetIrp(info->MediaChangeRequest); + IoFreeMdl(info->Gesn.Mdl); + irp->MdlAddress = NULL; + } + IoFreeIrp(info->MediaChangeSyncIrp); + FREE_POOL(info->Gesn.Buffer); + FREE_POOL(info->SenseBuffer); + + if (info->DisplayStateCallbackHandle) + { + PoUnregisterPowerSettingCallback(info->DisplayStateCallbackHandle); + info->DisplayStateCallbackHandle = NULL; + } + + FREE_POOL(info); + + DeviceExtension->MediaChangeDetectionInfo = NULL; + + return; +} // end DeviceReleaseMcnResources() + + +IO_COMPLETION_ROUTINE RequestMcnSyncIrpCompletion; + +NTSTATUS +NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ +RequestMcnSyncIrpCompletion( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _In_reads_opt_(_Inexpressible_("varies")) PVOID Context + ) +/*++ + +Routine Description: + + The MCN work finishes, reset the fields to allow another MCN request + be scheduled. + +Arguments: + + DeviceObject - device that the completion routine fires on. + + Irp - The irp to be completed. + + Context - IRP context + +Return Value: + NTSTATUS + +--*/ +{ + PCDROM_DEVICE_EXTENSION DeviceExtension = NULL; + PMEDIA_CHANGE_DETECTION_INFO info = NULL; + + if (Context == NULL) + { + // this will never happen, but code must be there to prevent OACR warnings. + return STATUS_MORE_PROCESSING_REQUIRED; + } + + DeviceExtension = (PCDROM_DEVICE_EXTENSION) Context; + info = DeviceExtension->MediaChangeDetectionInfo; + +#ifndef DEBUG + UNREFERENCED_PARAMETER(Irp); +#endif + UNREFERENCED_PARAMETER(DeviceObject); + + NT_ASSERT(Irp == info->MediaChangeSyncIrp); + + IoReuseIrp(info->MediaChangeSyncIrp, STATUS_NOT_SUPPORTED); + + // reset the value to let timer routine be able to send the next request. + InterlockedCompareExchange((PLONG)&(info->MediaChangeRequestInUse), 0, 1); + + return STATUS_MORE_PROCESSING_REQUIRED; +} + + +VOID +RequestSetupMcnSyncIrp( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + setup the MCN synchronization irp. + +Arguments: + + DeviceExtension - the device context + +Return Value: + None + +--*/ +{ + PIRP irp = NULL; + PIO_STACK_LOCATION irpStack = NULL; + PIO_STACK_LOCATION nextIrpStack = NULL; + + irp = DeviceExtension->MediaChangeDetectionInfo->MediaChangeSyncIrp; + NT_ASSERT(irp != NULL); + + // + // For the driver that creates an IRP, there is no 'current' stack location. + // Step down one IRP stack location so that the extra top one + // becomes our 'current' one. + // + IoSetNextIrpStackLocation(irp); + + /* + * Cache our device object in the extra top IRP stack location + * so we have it in our completion routine. + */ + irpStack = IoGetCurrentIrpStackLocation(irp); + irpStack->DeviceObject = DeviceExtension->DeviceObject; + + // + // If the irp is sent down when the volume needs to be + // verified, CdRomUpdateGeometryCompletion won't complete + // it since it's not associated with a thread. Marking + // it to override the verify causes it always be sent + // to the port driver + // + nextIrpStack = IoGetNextIrpStackLocation(irp); + + SET_FLAG(nextIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME); + + nextIrpStack->MajorFunction = IRP_MJ_DEVICE_CONTROL; + // pick up this IOCTL code as it's not normaly seen for CD/DVD drive and does not require input. + // set other fields to make this IOCTL recognizable by CDROM.SYS + nextIrpStack->Parameters.Others.Argument1 = RequestSetupMcnSyncIrp; + nextIrpStack->Parameters.Others.Argument2 = RequestSetupMcnSyncIrp; + nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_MCN_SYNC_FAKE_IOCTL; //Argument3. + nextIrpStack->Parameters.Others.Argument4 = RequestSetupMcnSyncIrp; + + IoSetCompletionRoutine(irp, + RequestMcnSyncIrpCompletion, + DeviceExtension, + TRUE, + TRUE, + TRUE); + + return; +} + + +VOID +NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ +DeviceMainTimerTickHandler( + _In_ WDFTIMER Timer + ) +/*++ + +Routine Description: + + This routine setup a sync irp and send it to the serial queue. + Serial queue will process MCN when receive this sync irp. + +Arguments: + + Timer - the timer object that fires. + +Return Value: + None + +--*/ +{ + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + size_t dataLength = 0; + + deviceExtension = WdfObjectGetTypedContext(WdfTimerGetParentObject(Timer), CDROM_DEVICE_EXTENSION); + + (void) RequestHandleEventNotification(deviceExtension, NULL, NULL, &dataLength); + + return; +} // end DeviceMainTimerTickHandler() + + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceEnableMainTimer( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + This routine will allocate timer related resources on the first time call. + Start the timer. + +Arguments: + + DeviceExtension - the device context + +Return Value: + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + if ((DeviceExtension->MediaChangeDetectionInfo == NULL) || + (DeviceExtension->MediaChangeDetectionInfo->AsynchronousNotificationSupported != FALSE)) + { + // Asynchronous Notification is enabled, timer not needed. + return status; + } + + if (DeviceExtension->MainTimer == NULL) + { + //create main timer object. + WDF_TIMER_CONFIG timerConfig; + WDF_OBJECT_ATTRIBUTES timerAttributes; + + WDF_TIMER_CONFIG_INIT(&timerConfig, DeviceMainTimerTickHandler); + + // Polling frequently on virtual optical devices created by Hyper-V will + // cause a significant perf / power hit. These devices need to be polled + // less frequently for device state changes. + if (TEST_FLAG(DeviceExtension->DeviceAdditionalData.HackFlags, CDROM_HACK_MSFT_VIRTUAL_ODD)) + { + timerConfig.Period = 2000; // 2 seconds, in milliseconds. + } + else + { + timerConfig.Period = 1000; // 1 second, in milliseconds. + } + + timerConfig.TolerableDelay = 500; // 0.5 seconds, in milliseconds + + //Set the autoSerialization to FALSE, as the parent device's + //execute level is WdfExecutionLevelPassive. + timerConfig.AutomaticSerialization = FALSE; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = DeviceExtension->Device; + timerAttributes.ExecutionLevel = WdfExecutionLevelInheritFromParent; + + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &DeviceExtension->MainTimer); + } + + if (NT_SUCCESS(status)) + { + WdfTimerStart(DeviceExtension->MainTimer,WDF_REL_TIMEOUT_IN_MS(100)); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceEnableMainTimer: Once a second timer enabled for WDFDEVICE %p\n", + DeviceExtension->Device)); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceEnableMainTimer: WDFDEVICE %p, Status %lx initializing timer\n", + DeviceExtension->Device, status)); + } + + return status; +} // end DeviceEnableMainTimer() + + +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +DeviceDisableMainTimer( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ + +Routine Description: + + stop the timer. + +Arguments: + + DeviceExtension - device context + +Return Value: + None + +--*/ +{ + PAGED_CODE(); + + if ((DeviceExtension->MediaChangeDetectionInfo == NULL) || + (DeviceExtension->MediaChangeDetectionInfo->AsynchronousNotificationSupported != FALSE)) + { + // Asynchronous Notification is enabled, timer not needed. + return; + } + + if (DeviceExtension->MainTimer != NULL) + { + // + // we are only going to stop the actual timer in remove device routine. + // it is the responsibility of the code within the timer routine to + // check if the device is removed and not processing io for the final + // call. + // this keeps the code clean and prevents lots of bugs. + // + WdfTimerStop(DeviceExtension->MainTimer,TRUE); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, + "DeviceDisableMainTimer: Once a second timer disabled for device %p\n", + DeviceExtension->Device)); + } + else + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, + "DeviceDisableMainTimer: Timer never enabled\n")); + } + + return; +} // end DeviceDisableMainTimer() + + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +RequestHandleMcnControl( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request, + _Out_ size_t * DataLength + ) +/*++ + +Routine Description: + + This routine handles the process of IOCTL_STORAGE_MCN_CONTROL + +Arguments: + + DeviceExtension - device context + + Request - request object + + RequestParameters - request parameters + + DataLength - data transferred + +Return Value: + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + WDFFILEOBJECT fileObject = NULL; + PFILE_OBJECT_CONTEXT fileObjectContext = NULL; + PPREVENT_MEDIA_REMOVAL mediaRemoval = NULL; + + PAGED_CODE(); + + *DataLength = 0; + + status = WdfRequestRetrieveInputBuffer(Request, + sizeof(PREVENT_MEDIA_REMOVAL), + &mediaRemoval, + NULL); + + if (NT_SUCCESS(status)) + { + fileObject = WdfRequestGetFileObject(Request); + + // Check to make sure we have a file object extension to keep track of this + // request. If not we'll fail it before synchronizing. + if (fileObject != NULL) + { + fileObjectContext = FileObjectGetContext(fileObject); + } + + if ((fileObjectContext == NULL) && + (WdfRequestGetRequestorMode(Request) == KernelMode)) + { + fileObjectContext = &DeviceExtension->KernelModeMcnContext; + } + + if (fileObjectContext == NULL) + { + // This handle isn't setup correctly. We can't let the + // operation go. + status = STATUS_INVALID_PARAMETER; + } + } + + if (NT_SUCCESS(status)) + { + if (mediaRemoval->PreventMediaRemoval) + { + // This is a lock command. Reissue the command in case bus or + // device was reset and the lock was cleared. + DeviceDisableMediaChangeDetection(DeviceExtension, fileObjectContext); + } + else + { + if (fileObjectContext->McnDisableCount == 0) + { + status = STATUS_INVALID_DEVICE_STATE; + } + else + { + DeviceEnableMediaChangeDetection(DeviceExtension, fileObjectContext, TRUE); + } + } + } + + return status; +} // end RequestHandleMcnControl() + +#pragma warning(pop) // un-sets any local warning changes + diff --git a/drivers/storage/class/cdrom_new/cdrom.c b/drivers/storage/class/cdrom_new/cdrom.c new file mode 100644 index 00000000000..ac0c9c63090 --- /dev/null +++ b/drivers/storage/class/cdrom_new/cdrom.c @@ -0,0 +1,4242 @@ +/*-- + +Copyright (C) Microsoft Corporation. All rights reserved. + +Module Name: + + cdrom.c + +Abstract: + + The CDROM class driver tranlates IRPs to SRBs with embedded CDBs + and sends them to its devices through the port driver. + +Environment: + + kernel mode only + +Notes: + + +Revision History: + +--*/ + +// this definition is used to link _StorDebugPrint() function. +#define DEBUG_MAIN_SOURCE 1 + + +#include "ntddk.h" +#include "ntstrsafe.h" + +#include "ntddstor.h" +#include "ntddtape.h" +#include "wdfcore.h" +#include "devpkey.h" + +#include "cdrom.h" +#include "ioctl.h" +#include "mmc.h" +#include "scratch.h" + + +#ifdef DEBUG_USE_WPP +#include "cdrom.tmh" +#endif + +BOOLEAN +BootEnvironmentIsWinPE( + VOID + ); + +#ifdef ALLOC_PRAGMA + +#pragma alloc_text(INIT, DriverEntry) +#pragma alloc_text(INIT, BootEnvironmentIsWinPE) + +#pragma alloc_text(PAGE, DriverEvtCleanup) +#pragma alloc_text(PAGE, DriverEvtDeviceAdd) +#pragma alloc_text(PAGE, DeviceEvtCleanup) +#pragma alloc_text(PAGE, DeviceEvtSelfManagedIoCleanup) +#pragma alloc_text(PAGE, DeviceEvtD0Exit) +#pragma alloc_text(PAGE, CreateQueueEvtIoDefault) +#pragma alloc_text(PAGE, DeviceEvtFileClose) +#pragma alloc_text(PAGE, DeviceCleanupProtectedLocks) +#pragma alloc_text(PAGE, DeviceCleanupDisableMcn) +#pragma alloc_text(PAGE, RequestProcessSerializedIoctl) +#pragma alloc_text(PAGE, ReadWriteWorkItemRoutine) +#pragma alloc_text(PAGE, IoctlWorkItemRoutine) +#pragma alloc_text(PAGE, DeviceEvtSurpriseRemoval) + +#endif + +NTSTATUS +NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + + Installable driver initialization entry point. + This entry point is called directly by the I/O system. + +Arguments: + + DriverObject - pointer to the driver object + + RegistryPath - pointer to a unicode string representing the path, + to driver-specific key in the registry. + +Return Value: + + STATUS_SUCCESS if successful, + STATUS_UNSUCCESSFUL otherwise. + +--*/ +{ + NTSTATUS status; + WDF_DRIVER_CONFIG config; + WDF_OBJECT_ATTRIBUTES attributes; + WDFDRIVER driverObject = NULL; + + PAGED_CODE(); + + // Initialize WPP Tracing + WPP_INIT_TRACING(DriverObject, RegistryPath); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "CDROM.SYS DriverObject %p loading\n", + DriverObject)); + + // Register DeviceAdd and DriverEvtCleanup callback. + // WPP_CLEANUP will be called in DriverEvtCleanup + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CDROM_DRIVER_EXTENSION); + attributes.EvtCleanupCallback = DriverEvtCleanup; + + WDF_DRIVER_CONFIG_INIT(&config, DriverEvtDeviceAdd); + + status = WdfDriverCreate(DriverObject, + RegistryPath, + &attributes, + &config, + &driverObject); + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_INIT, + "WdfDriverCreate failed. %x\n", + status)); + + // Cleanup tracing here because DriverUnload will not be called + // as we have failed to create WDFDRIVER object itself. + WPP_CLEANUP(DriverObject); + + } + else + { + PCDROM_DRIVER_EXTENSION driverExtension = DriverGetExtension(driverObject); + + // Copy the registry path into the driver extension so we can use it later + driverExtension->Version = 0x01; + driverExtension->DriverObject = DriverObject; + + if (BootEnvironmentIsWinPE()) { + + SET_FLAG(driverExtension->Flags, CDROM_FLAG_WINPE_MODE); + } + + } + + return status; +} + + +BOOLEAN +BootEnvironmentIsWinPE( + VOID + ) +/*++ + +Routine Description: + + This routine determines if the boot enviroment is WinPE + +Arguments: + + None + +Return Value: + + BOOLEAN - TRUE if the environment is WinPE; FALSE otherwise + +--*/ +{ + NTSTATUS status; + WDFKEY registryKey = NULL; + + DECLARE_CONST_UNICODE_STRING(registryKeyName, WINPE_REG_KEY_NAME); + + PAGED_CODE(); + + status = WdfRegistryOpenKey(NULL, + ®istryKeyName, + KEY_READ, + WDF_NO_OBJECT_ATTRIBUTES, + ®istryKey); + + if (!NT_SUCCESS(status)) + { + return FALSE; + } + + WdfRegistryClose(registryKey); + return TRUE; +} // end BootEnvironmentIsWinPE() + + +VOID +NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ +DriverEvtCleanup( + _In_ WDFOBJECT Driver + ) +/*++ +Routine Description: + + Free all the resources allocated in DriverEntry. + +Arguments: + + Driver - handle to a WDF Driver object. + +Return Value: + + VOID. + +--*/ +{ + WDFDRIVER driver = (WDFDRIVER)Driver; + + PAGED_CODE (); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "CDROM.SYS DriverObject %p cleanup. Will be unloaded soon\n", + driver)); + + // Stop WPP Tracing + WPP_CLEANUP( WdfDriverWdmGetDriverObject(driver) ); + + + return; +} + + +NTSTATUS +NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ +DriverEvtDeviceAdd( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit + ) +/*++ + +Routine Description: + + EvtDeviceAdd is called by the framework in response to AddDevice + call from the PnP manager. + + +Arguments: + + Driver - Handle to a framework driver object created in DriverEntry + + DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PCDROM_DRIVER_EXTENSION driverExtension = NULL; + WDFDEVICE device = NULL; + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + BOOLEAN deviceClaimed = FALSE; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_FILEOBJECT_CONFIG fileObjectConfig; + WDF_IO_TARGET_OPEN_PARAMS ioTargetOpenParams; + WDF_IO_QUEUE_CONFIG queueConfig; + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_REMOVE_LOCK_OPTIONS removeLockOptions; + PWCHAR wideDeviceName = NULL; + UNICODE_STRING unicodeDeviceName; + PDEVICE_OBJECT lowerPdo = NULL; + ULONG deviceNumber = 0; + ULONG devicePropertySessionId = INVALID_SESSION; + ULONG devicePropertySize = 0; + DEVPROPTYPE devicePropertyType = DEVPROP_TYPE_EMPTY; + + PAGED_CODE(); + + driverExtension = DriverGetExtension(Driver); + + // 0. Initialize the objects that we're going to use + RtlInitUnicodeString(&unicodeDeviceName, NULL); + + // 1. Register PnP&Power callbacks for any we are interested in. + // If a callback isn't set, Framework will take the default action by itself. + { + // Zero out the PnpPowerCallbacks structure. + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + + // Use this callback to init resources that are used by the device and only needs to be called once. + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = DeviceEvtSelfManagedIoInit; + + // Use this callback to prepare device for coming back from a lower power mode to D0. + pnpPowerCallbacks.EvtDeviceD0Entry = DeviceEvtD0Entry; + + // Use this callback to prepare device for entering into a lower power mode. + pnpPowerCallbacks.EvtDeviceD0Exit = DeviceEvtD0Exit; + + // Use this callback to free any resources used by device and will be called when the device is + // powered down. + pnpPowerCallbacks.EvtDeviceSelfManagedIoCleanup = DeviceEvtSelfManagedIoCleanup; + + pnpPowerCallbacks.EvtDeviceSurpriseRemoval = DeviceEvtSurpriseRemoval; + + // Register the PnP and power callbacks. + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + } + + // 2. Register the EvtIoInCallerContext to deal with IOCTLs that need to stay in original context. + WdfDeviceInitSetIoInCallerContextCallback(DeviceInit, + DeviceEvtIoInCallerContext); + + // 3. Register PreprocessCallback for IRP_MJ_POWER, IRP_MJ_FLUSH_BUFFERS and IRP_MJ_SHUTDOWN + { + UCHAR minorFunctions[1]; + + minorFunctions[0] = IRP_MN_SET_POWER; + + status = WdfDeviceInitAssignWdmIrpPreprocessCallback(DeviceInit, + RequestProcessSetPower, + IRP_MJ_POWER, + minorFunctions, + RTL_NUMBER_OF(minorFunctions)); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Assign IrpPreprocessCallback for IRP_MJ_POWER failed, " + "status: 0x%X\n", status)); ... 30678 lines suppressed ...