1603 lines
44 KiB
C
1603 lines
44 KiB
C
|
/*++
|
|||
|
Copyright (C) Microsoft Corporation, 1998 - 1999
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
RedBook.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
kernel mode only
|
|||
|
|
|||
|
Notes:
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include "redbook.h"
|
|||
|
#include "ntddredb.h"
|
|||
|
#include "proto.h"
|
|||
|
#include <scsi.h> // for SetKnownGoodDrive()
|
|||
|
#include <stdio.h> // vsprintf()
|
|||
|
|
|||
|
#include "ioctl.tmh"
|
|||
|
|
|||
|
#ifdef ALLOC_PRAGMA
|
|||
|
#pragma alloc_text(PAGE, RedBookCheckForDiscChangeAndFreeResources )
|
|||
|
#pragma alloc_text(PAGE, RedBookCompleteIoctl )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCCheckVerify )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCDefault )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCGetVolume )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCPause )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCPlay )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCReadQ )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCResume )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCSeek )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCSetVolume )
|
|||
|
#pragma alloc_text(PAGE, RedBookDCStop )
|
|||
|
#pragma alloc_text(PAGE, RedBookThreadIoctlCompletionHandler )
|
|||
|
#pragma alloc_text(PAGE, RedBookThreadIoctlHandler )
|
|||
|
#pragma alloc_text(PAGE, WhichTrackContainsThisLBA )
|
|||
|
#endif // ALLOC_PRAGMA
|
|||
|
|
|||
|
////////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
RedBookDeviceControl(
|
|||
|
PDEVICE_OBJECT DeviceObject,
|
|||
|
PIRP Irp
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called by the I/O subsystem for device controls.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DeviceObject
|
|||
|
Irp
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
PREDBOOK_DEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
|
|||
|
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation( Irp );
|
|||
|
ULONG cdromState;
|
|||
|
NTSTATUS status;
|
|||
|
|
|||
|
BOOLEAN putOnQueue = FALSE;
|
|||
|
BOOLEAN completeRequest = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// ioctls not guaranteed at passive, making this whole
|
|||
|
// section non-paged
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Prevent a remove from occuring while IO pending
|
|||
|
//
|
|||
|
|
|||
|
status = IoAcquireRemoveLock( &deviceExtension->RemoveLock, Irp );
|
|||
|
|
|||
|
if ( !NT_SUCCESS(status) ) {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DeviceControl !! Unable to acquire remove lock %lx\n",
|
|||
|
status));
|
|||
|
Irp->IoStatus.Information = 0;
|
|||
|
Irp->IoStatus.Status = status;
|
|||
|
IoCompleteRequest( Irp, IO_CD_ROM_INCREMENT );
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
#if DBG
|
|||
|
//
|
|||
|
// save some info for the last N device ioctls that came through
|
|||
|
//
|
|||
|
{
|
|||
|
ULONG index;
|
|||
|
ULONG sizeToCopy;
|
|||
|
ULONG stacksToCopy;
|
|||
|
PSAVED_IO savedIo;
|
|||
|
|
|||
|
index = InterlockedIncrement(&deviceExtension->SavedIoCurrentIndex);
|
|||
|
index %= SAVED_IO_MAX;
|
|||
|
|
|||
|
savedIo = &(deviceExtension->SavedIo[index]);
|
|||
|
|
|||
|
//
|
|||
|
// copy as much of the irp as we can....
|
|||
|
//
|
|||
|
|
|||
|
savedIo->OriginalIrp = Irp;
|
|||
|
if (Irp->StackCount > 7) {
|
|||
|
|
|||
|
sizeToCopy = IoSizeOfIrp(8);
|
|||
|
RtlFillMemory(savedIo, sizeToCopy, 0xff);
|
|||
|
sizeToCopy -= sizeof(IO_STACK_LOCATION);
|
|||
|
RtlCopyMemory(savedIo, Irp, sizeToCopy);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
sizeToCopy = IoSizeOfIrp(Irp->StackCount);
|
|||
|
RtlZeroMemory(savedIo, sizeof(SAVED_IO));
|
|||
|
RtlCopyMemory(savedIo, Irp, sizeToCopy);
|
|||
|
|
|||
|
}
|
|||
|
} // end of saved io
|
|||
|
#endif // DBG
|
|||
|
|
|||
|
//
|
|||
|
// if handled, just verify the paramters in this routine.
|
|||
|
//
|
|||
|
|
|||
|
status = STATUS_UNSUCCESSFUL;
|
|||
|
cdromState = GetCdromState(deviceExtension);
|
|||
|
|
|||
|
switch ( currentIrpStack->Parameters.DeviceIoControl.IoControlCode ) {
|
|||
|
|
|||
|
case IOCTL_CDROM_PAUSE_AUDIO: {
|
|||
|
if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
Irp->IoStatus.Information = 0;
|
|||
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_STOP_AUDIO: {
|
|||
|
if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
Irp->IoStatus.Information = 0;
|
|||
|
status = STATUS_SUCCESS;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_RESUME_AUDIO: {
|
|||
|
if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
Irp->IoStatus.Information = 0;
|
|||
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_PLAY_AUDIO_MSF: {
|
|||
|
if (currentIrpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|||
|
sizeof(CDROM_PLAY_AUDIO_MSF)) {
|
|||
|
Irp->IoStatus.Information = sizeof(CDROM_PLAY_AUDIO_MSF);
|
|||
|
status = STATUS_BUFFER_TOO_SMALL;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_SEEK_AUDIO_MSF: {
|
|||
|
if (currentIrpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|||
|
sizeof(CDROM_SEEK_AUDIO_MSF)) {
|
|||
|
Irp->IoStatus.Information = sizeof(CDROM_SEEK_AUDIO_MSF);
|
|||
|
status = STATUS_BUFFER_TOO_SMALL;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
// default -- passthrough
|
|||
|
// REQUIRED to reduce latency for some drives.
|
|||
|
// drives may still fail the request
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_READ_Q_CHANNEL: {
|
|||
|
|
|||
|
PCDROM_SUB_Q_DATA_FORMAT inputBuffer;
|
|||
|
|
|||
|
inputBuffer = Irp->AssociatedIrp.SystemBuffer;
|
|||
|
|
|||
|
if (currentIrpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|||
|
sizeof(SUB_Q_CHANNEL_DATA)) {
|
|||
|
Irp->IoStatus.Information = sizeof(SUB_Q_CHANNEL_DATA);
|
|||
|
status = STATUS_BUFFER_TOO_SMALL;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else if (inputBuffer->Format != IOCTL_CDROM_CURRENT_POSITION &&
|
|||
|
inputBuffer->Format != IOCTL_CDROM_MEDIA_CATALOG &&
|
|||
|
inputBuffer->Format != IOCTL_CDROM_TRACK_ISRC ) {
|
|||
|
Irp->IoStatus.Information = 0;
|
|||
|
status = STATUS_INVALID_PARAMETER;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
// default -- passthrough
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_SET_VOLUME: {
|
|||
|
|
|||
|
if (currentIrpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|||
|
sizeof(VOLUME_CONTROL)) {
|
|||
|
Irp->IoStatus.Information = sizeof(VOLUME_CONTROL);
|
|||
|
status = STATUS_BUFFER_TOO_SMALL;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
// default -- passthrough
|
|||
|
// BUGBUG -- this should set our internal volume
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_GET_VOLUME: {
|
|||
|
|
|||
|
if (currentIrpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|||
|
sizeof(VOLUME_CONTROL)) {
|
|||
|
Irp->IoStatus.Information = sizeof(VOLUME_CONTROL);
|
|||
|
status = STATUS_BUFFER_TOO_SMALL;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
// default -- passthrough
|
|||
|
// BUGBUG -- this should return our internal volume
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_STORAGE_CHECK_VERIFY2:
|
|||
|
case IOCTL_STORAGE_CHECK_VERIFY:
|
|||
|
case IOCTL_CDROM_CHECK_VERIFY:
|
|||
|
case IOCTL_DISK_CHECK_VERIFY: {
|
|||
|
|
|||
|
if ((currentIrpStack->Parameters.DeviceIoControl.OutputBufferLength) &&
|
|||
|
(currentIrpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|||
|
sizeof(ULONG))) {
|
|||
|
Irp->IoStatus.Information = sizeof(ULONG);
|
|||
|
status = STATUS_BUFFER_TOO_SMALL;
|
|||
|
completeRequest = TRUE;
|
|||
|
} else if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
// default -- passthrough
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
|
|||
|
if (TEST_FLAG(cdromState, CD_STOPPED)) {
|
|||
|
// default -- passthrough
|
|||
|
} else {
|
|||
|
putOnQueue = TRUE;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (putOnQueue) {
|
|||
|
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA ioctlData;
|
|||
|
|
|||
|
ASSERT(completeRequest == FALSE);
|
|||
|
|
|||
|
//
|
|||
|
// need to allocate some info for each ioctl we handle
|
|||
|
//
|
|||
|
|
|||
|
ioctlData =
|
|||
|
(PREDBOOK_THREAD_IOCTL_DATA)ExAllocatePoolWithTag(
|
|||
|
NonPagedPool,
|
|||
|
sizeof(REDBOOK_THREAD_IOCTL_DATA),
|
|||
|
TAG_T_IOCTL);
|
|||
|
if (ioctlData == NULL) {
|
|||
|
Irp->IoStatus.Information = 0;
|
|||
|
Irp->IoStatus.Status = STATUS_NO_MEMORY;
|
|||
|
IoCompleteRequest(Irp, IO_CD_ROM_INCREMENT);
|
|||
|
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
|
|||
|
return STATUS_NO_MEMORY;
|
|||
|
}
|
|||
|
|
|||
|
RtlZeroMemory(ioctlData, sizeof(REDBOOK_THREAD_IOCTL_DATA));
|
|||
|
ioctlData->Irp = Irp;
|
|||
|
ioctlData->Irp->IoStatus.Status = STATUS_PENDING;
|
|||
|
IoMarkIrpPending(ioctlData->Irp);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DeviceControl => Queue Ioctl Irp %p (%p)\n",
|
|||
|
ioctlData->Irp, ioctlData));
|
|||
|
|
|||
|
//
|
|||
|
// queue them, allow thread to handle request
|
|||
|
//
|
|||
|
|
|||
|
ExInterlockedInsertTailList(&deviceExtension->Thread.IoctlList,
|
|||
|
&ioctlData->ListEntry,
|
|||
|
&deviceExtension->Thread.IoctlLock);
|
|||
|
KeSetEvent(deviceExtension->Thread.Events[EVENT_IOCTL],
|
|||
|
IO_NO_INCREMENT, FALSE);
|
|||
|
|
|||
|
|
|||
|
status = STATUS_PENDING;
|
|||
|
|
|||
|
} else if (completeRequest) {
|
|||
|
|
|||
|
ASSERT(putOnQueue == FALSE);
|
|||
|
|
|||
|
//
|
|||
|
// some error, ie. invalid buffer length
|
|||
|
//
|
|||
|
if (!NT_SUCCESS(status)) {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DeviceControl => Completing Irp %p with error %x\n",
|
|||
|
Irp, status));
|
|||
|
} else {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DeviceControl => Completing Irp %p early?\n",
|
|||
|
Irp));
|
|||
|
}
|
|||
|
Irp->IoStatus.Status = status;
|
|||
|
|
|||
|
IoCompleteRequest(Irp, IO_CD_ROM_INCREMENT);
|
|||
|
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// pass it through
|
|||
|
//
|
|||
|
|
|||
|
status = RedBookSendToNextDriver(DeviceObject, Irp);
|
|||
|
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return status;
|
|||
|
}
|
|||
|
////////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookThreadIoctlCompletionHandler(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension
|
|||
|
)
|
|||
|
{
|
|||
|
PIO_STACK_LOCATION irpStack;
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA ioctlData;
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
ioctlData = CONTAINING_RECORD(DeviceExtension->Thread.IoctlCurrent,
|
|||
|
REDBOOK_THREAD_IOCTL_DATA,
|
|||
|
ListEntry);
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
irpStack = IoGetCurrentIrpStackLocation(ioctlData->Irp);
|
|||
|
|
|||
|
//
|
|||
|
// final state should be set by the digital handler
|
|||
|
//
|
|||
|
|
|||
|
switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
|
|||
|
|
|||
|
case IOCTL_CDROM_PAUSE_AUDIO: {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"IoctlComp => Finishing pause %p\n", ioctlData->Irp));
|
|||
|
|
|||
|
ASSERT(state == CD_PAUSED);
|
|||
|
|
|||
|
ioctlData->Irp->IoStatus.Information = 0;
|
|||
|
ioctlData->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, ioctlData, FALSE);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_SEEK_AUDIO_MSF: {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"IoctlComp => Finishing seek %p\n", ioctlData->Irp));
|
|||
|
|
|||
|
ASSERT(state == CD_PLAYING);
|
|||
|
|
|||
|
DeviceExtension->Buffer.FirstPause = 1;
|
|||
|
KeSetEvent(DeviceExtension->Thread.Events[EVENT_DIGITAL],
|
|||
|
IO_CD_ROM_INCREMENT, FALSE);
|
|||
|
ioctlData->Irp->IoStatus.Information = 0;
|
|||
|
ioctlData->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, ioctlData, FALSE);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_STOP_AUDIO: {
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"IoctlComp => Finishing stop %p\n", ioctlData->Irp));
|
|||
|
|
|||
|
ASSERT(TEST_FLAG(state, CD_STOPPED));
|
|||
|
|
|||
|
ioctlData->Irp->IoStatus.Information = 0;
|
|||
|
ioctlData->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, ioctlData, FALSE);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"IoctlComp => Unhandled Irp %p\n", ioctlData->Irp));
|
|||
|
ASSERT(FALSE);
|
|||
|
|
|||
|
ioctlData->Irp->IoStatus.Information = 0;
|
|||
|
ioctlData->Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, ioctlData, FALSE);
|
|||
|
break;
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
RedBookCompleteIoctl(
|
|||
|
IN PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
IN PREDBOOK_THREAD_IOCTL_DATA Context,
|
|||
|
IN BOOLEAN SendToLowerDriver
|
|||
|
)
|
|||
|
{
|
|||
|
PIRP irp = Context->Irp;
|
|||
|
|
|||
|
//
|
|||
|
// only to be called from the thread
|
|||
|
//
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
//
|
|||
|
// should be properly setup for completion
|
|||
|
//
|
|||
|
|
|||
|
if (DeviceExtension->Thread.IoctlCurrent == &Context->ListEntry) {
|
|||
|
|
|||
|
//
|
|||
|
// an ioctl that required post-processing is finished.
|
|||
|
// allow the next one to occur
|
|||
|
//
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"CompleteIoctl => state-changing Irp %p completed\n",
|
|||
|
irp));
|
|||
|
DeviceExtension->Thread.IoctlCurrent = NULL;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
ExFreePool(Context);
|
|||
|
Context = NULL;
|
|||
|
|
|||
|
if (SendToLowerDriver) {
|
|||
|
NTSTATUS status;
|
|||
|
status = RedBookSendToNextDriver(DeviceExtension->SelfDeviceObject, irp);
|
|||
|
IoReleaseRemoveLock(&DeviceExtension->RemoveLock, irp);
|
|||
|
return status;
|
|||
|
|
|||
|
|
|||
|
} else {
|
|||
|
IoCompleteRequest(irp, IO_CD_ROM_INCREMENT);
|
|||
|
IoReleaseRemoveLock(&DeviceExtension->RemoveLock, irp);
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookThreadIoctlHandler(
|
|||
|
IN PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
IN PLIST_ENTRY ListEntry
|
|||
|
)
|
|||
|
{
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA data;
|
|||
|
PIO_STACK_LOCATION currentIrpStack;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
//
|
|||
|
// should never happen if a state-changing ioctl is in progress
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(DeviceExtension->Thread.IoctlCurrent == NULL);
|
|||
|
|
|||
|
//
|
|||
|
// don't use stale info
|
|||
|
//
|
|||
|
|
|||
|
RedBookCheckForDiscChangeAndFreeResources(DeviceExtension);
|
|||
|
|
|||
|
//
|
|||
|
// get the ioctl that set this event and
|
|||
|
// start working on state changes neccessary
|
|||
|
//
|
|||
|
|
|||
|
data = CONTAINING_RECORD(ListEntry, REDBOOK_THREAD_IOCTL_DATA, ListEntry);
|
|||
|
|
|||
|
currentIrpStack = IoGetCurrentIrpStackLocation(data->Irp);
|
|||
|
|
|||
|
//
|
|||
|
// now guaranteed it's ok to run this ioctl
|
|||
|
// it's the responsibility of these routines to call RedBookCompleteIoctl()
|
|||
|
// *** OR *** to set DeviceExtension->Thread.IoctlCurrent to
|
|||
|
// Context->ListEntry if it requires post-processing, as this
|
|||
|
// is the mechanism used to determine the ioctl is still inprogress
|
|||
|
//
|
|||
|
|
|||
|
switch (currentIrpStack->Parameters.DeviceIoControl.IoControlCode) {
|
|||
|
|
|||
|
case IOCTL_CDROM_PLAY_AUDIO_MSF: {
|
|||
|
RedBookDCPlay(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_PAUSE_AUDIO: {
|
|||
|
RedBookDCPause(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_RESUME_AUDIO: {
|
|||
|
RedBookDCResume(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_STOP_AUDIO: {
|
|||
|
RedBookDCStop(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_SEEK_AUDIO_MSF: {
|
|||
|
RedBookDCSeek(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_READ_Q_CHANNEL: {
|
|||
|
RedBookDCReadQ(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_SET_VOLUME: {
|
|||
|
RedBookDCSetVolume(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_GET_VOLUME: {
|
|||
|
RedBookDCGetVolume(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case IOCTL_CDROM_CHECK_VERIFY:
|
|||
|
case IOCTL_DISK_CHECK_VERIFY:
|
|||
|
case IOCTL_STORAGE_CHECK_VERIFY:
|
|||
|
case IOCTL_STORAGE_CHECK_VERIFY2: {
|
|||
|
RedBookDCCheckVerify(DeviceExtension, data);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
data->Irp->IoStatus.Status = STATUS_DEVICE_BUSY;
|
|||
|
data->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, data, TRUE);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
////////////////////////////////////////////////////////////////////////////////
|
|||
|
////////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCCheckVerify(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PIO_STACK_LOCATION currentIrpStack;
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
currentIrpStack = IoGetCurrentIrpStackLocation(Context->Irp);
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCCheckVerify => not playing\n"));
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, TRUE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// data buffer is optional for this ioctl
|
|||
|
//
|
|||
|
|
|||
|
if (currentIrpStack->Parameters.DeviceIoControl.OutputBufferLength) {
|
|||
|
|
|||
|
*((PULONG)Context->Irp->AssociatedIrp.SystemBuffer) =
|
|||
|
DeviceExtension->CDRom.CheckVerify;
|
|||
|
Context->Irp->IoStatus.Information = sizeof(ULONG);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCDefault(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
//
|
|||
|
// IOCTLs are not all guaranteed to be called at passive irql,
|
|||
|
// so this can never be paged code.
|
|||
|
// there is a window of opportunity to send an ioctl while playing
|
|||
|
// audio digitally, but it can be ignored. this allows much more
|
|||
|
// pagable code.
|
|||
|
//
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls
|
|||
|
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, TRUE);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Complete the Irp
|
|||
|
//
|
|||
|
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_DEVICE_BUSY;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
|
|||
|
}
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCGetVolume(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PIO_STACK_LOCATION currentIrpStack;
|
|||
|
NTSTATUS status;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCGetVolume => Entering %p\n", Context->Irp));
|
|||
|
|
|||
|
//
|
|||
|
// guaranteed the volume info will not change
|
|||
|
//
|
|||
|
|
|||
|
RtlCopyMemory(Context->Irp->AssociatedIrp.SystemBuffer, // to
|
|||
|
&DeviceExtension->CDRom.Volume, // from
|
|||
|
sizeof(VOLUME_CONTROL));
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCGetVolume => volume was:"
|
|||
|
" (hex) %2x %2x %2x %2x\n",
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[0],
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[1],
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[2],
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[3]));
|
|||
|
|
|||
|
//
|
|||
|
// Complete the Irp (IoStatus.Information set above)
|
|||
|
//
|
|||
|
|
|||
|
Context->Irp->IoStatus.Information = sizeof(VOLUME_CONTROL);
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCPause(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPause => Entering %p\n", Context->Irp));
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING)) {
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPause => Not playing\n"));
|
|||
|
Context->Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, TRUE);
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
if (TEST_FLAG(state, CD_PAUSED)) {
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPause => Already paused %p\n", Context->Irp));
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// since setting to a temp state, it is not appropriate to
|
|||
|
// complete the irp until the operation itself completes.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(!TEST_FLAG(state, CD_PAUSING));
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPause => Starting pause %p\n", Context->Irp));
|
|||
|
DeviceExtension->Thread.IoctlCurrent = &Context->ListEntry;
|
|||
|
state = SetCdromState(DeviceExtension, state, state | CD_PAUSING);
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCPlay(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PCDROM_PLAY_AUDIO_MSF inputBuffer;
|
|||
|
PIO_STACK_LOCATION thisIrpStack;
|
|||
|
PIO_STACK_LOCATION nextIrpStack;
|
|||
|
PREVENT_MEDIA_REMOVAL mediaRemoval;
|
|||
|
ULONG sector;
|
|||
|
ULONG i;
|
|||
|
ULONG state;
|
|||
|
NTSTATUS status;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
inputBuffer = Context->Irp->AssociatedIrp.SystemBuffer;
|
|||
|
thisIrpStack = IoGetCurrentIrpStackLocation(Context->Irp);
|
|||
|
nextIrpStack = IoGetNextIrpStackLocation(Context->Irp);
|
|||
|
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPlay => Entering %p\n", Context->Irp));
|
|||
|
|
|||
|
status = RedBookCacheToc(DeviceExtension);
|
|||
|
|
|||
|
if (!NT_SUCCESS(status)) {
|
|||
|
Context->Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
sector = MSF_TO_LBA(inputBuffer->EndingM,
|
|||
|
inputBuffer->EndingS,
|
|||
|
inputBuffer->EndingF);
|
|||
|
|
|||
|
DeviceExtension->CDRom.EndPlay = sector;
|
|||
|
|
|||
|
sector = MSF_TO_LBA(inputBuffer->StartingM,
|
|||
|
inputBuffer->StartingS,
|
|||
|
inputBuffer->StartingF);
|
|||
|
|
|||
|
DeviceExtension->CDRom.NextToRead = sector;
|
|||
|
DeviceExtension->CDRom.NextToStream = sector;
|
|||
|
DeviceExtension->CDRom.FinishedStreaming = sector;
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the ending sector is within disc
|
|||
|
// bounds or return STATUS_INVALID_DEVICE_REQUEST?
|
|||
|
// this will prevent success on play, followed by an
|
|||
|
// immediate stop.
|
|||
|
//
|
|||
|
|
|||
|
if (0) {
|
|||
|
PCDROM_TOC toc = DeviceExtension->CDRom.Toc;
|
|||
|
LONG track;
|
|||
|
LONG endTrack;
|
|||
|
|
|||
|
//
|
|||
|
// ensure end has an lba greater than start
|
|||
|
//
|
|||
|
|
|||
|
if (DeviceExtension->CDRom.EndPlay <=
|
|||
|
DeviceExtension->CDRom.NextToRead) {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"Play => End sector (%x) must be more than start "
|
|||
|
"sector (%x)\n",
|
|||
|
DeviceExtension->CDRom.EndPlay,
|
|||
|
DeviceExtension->CDRom.NextToRead
|
|||
|
));
|
|||
|
Context->Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// what track(s) are we playing?
|
|||
|
//
|
|||
|
|
|||
|
track = WhichTrackContainsThisLBA(toc, DeviceExtension->CDRom.NextToRead);
|
|||
|
endTrack = WhichTrackContainsThisLBA(toc, DeviceExtension->CDRom.EndPlay);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"Play => Playing sector %x to %x (track %x to %x)\n",
|
|||
|
DeviceExtension->CDRom.NextToRead,
|
|||
|
DeviceExtension->CDRom.EndPlay,
|
|||
|
track,
|
|||
|
endTrack));
|
|||
|
|
|||
|
//
|
|||
|
// make sure the tracks are actually valid
|
|||
|
//
|
|||
|
|
|||
|
if (track < 0 ||
|
|||
|
endTrack < 0 ||
|
|||
|
endTrack <= (toc->LastTrack - toc->FirstTrack)
|
|||
|
) {
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"Play => Track %x is invalid\n", track));
|
|||
|
Context->Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
for (;track <= endTrack;track++) {
|
|||
|
if (toc->TrackData[track].Adr & AUDIO_DATA_TRACK) {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"Play => Track %x is not audio\n", track));
|
|||
|
Context->Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// if not paused, then state must equal stopped, which means we need
|
|||
|
// to allocate the resources.
|
|||
|
//
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
if (TEST_FLAG(state, CD_PAUSED)) {
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPlay => Resuming playback?\n"));
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// this function will allocate them iff they are not
|
|||
|
// already allocated.
|
|||
|
//
|
|||
|
|
|||
|
status = RedBookAllocatePlayResources(DeviceExtension);
|
|||
|
if (!NT_SUCCESS(status)) {
|
|||
|
Context->Irp->IoStatus.Status = STATUS_NO_MEMORY;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Set the new device state (thread will begin playing)
|
|||
|
//
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPlay => Setting state to CD_PLAYING\n"));
|
|||
|
state = SetCdromState(DeviceExtension, state, CD_PLAYING);
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCPlay => Exiting successfully\n"));
|
|||
|
|
|||
|
//
|
|||
|
// finish the request if it's a user
|
|||
|
// request for a new play operation
|
|||
|
//
|
|||
|
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCReadQ(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PCDROM_SUB_Q_DATA_FORMAT inputBuffer;
|
|||
|
PIO_STACK_LOCATION currentIrpStack;
|
|||
|
UCHAR formatCode;
|
|||
|
ULONG state;
|
|||
|
NTSTATUS status;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
inputBuffer = Context->Irp->AssociatedIrp.SystemBuffer;
|
|||
|
currentIrpStack = IoGetCurrentIrpStackLocation(Context->Irp);
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls
|
|||
|
|
|||
|
//
|
|||
|
// no need to handle this irp
|
|||
|
//
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCReadQ => Not playing\n"));
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, TRUE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (inputBuffer->Format != IOCTL_CDROM_CURRENT_POSITION) {
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_DEVICE_BUSY;
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCReadQ => Bad Format %x\n", inputBuffer->Format));
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// we are in the midst of playback or pause. fake the information
|
|||
|
// a real cdrom would have returned if it was playing audio at the
|
|||
|
// same location that we are currently at.
|
|||
|
//
|
|||
|
|
|||
|
{
|
|||
|
PSUB_Q_CURRENT_POSITION outputBuffer;
|
|||
|
PCDROM_TOC toc;
|
|||
|
ULONG lbaTrack;
|
|||
|
ULONG lbaRelative;
|
|||
|
ULONG instantLba;
|
|||
|
UCHAR timeAbsolute[3];
|
|||
|
UCHAR timeRelative[3];
|
|||
|
LONG trackNumber;
|
|||
|
|
|||
|
outputBuffer = Context->Irp->AssociatedIrp.SystemBuffer;
|
|||
|
RtlZeroMemory(outputBuffer,
|
|||
|
currentIrpStack->Parameters.DeviceIoControl.OutputBufferLength);
|
|||
|
|
|||
|
//
|
|||
|
// Still playing audio
|
|||
|
//
|
|||
|
|
|||
|
outputBuffer->Header.Reserved = 0;
|
|||
|
if (TEST_FLAG(state, CD_PAUSED)) {
|
|||
|
outputBuffer->Header.AudioStatus = AUDIO_STATUS_PAUSED;
|
|||
|
} else if (TEST_FLAG(state, CD_PLAYING)) {
|
|||
|
outputBuffer->Header.AudioStatus = AUDIO_STATUS_IN_PROGRESS;
|
|||
|
} else {
|
|||
|
ASSERT(!"State was invalid?");
|
|||
|
outputBuffer->Header.AudioStatus = AUDIO_STATUS_IN_PROGRESS;
|
|||
|
}
|
|||
|
outputBuffer->Header.DataLength[0] =
|
|||
|
(sizeof(SUB_Q_CURRENT_POSITION) - sizeof(SUB_Q_HEADER)) >> 8;
|
|||
|
outputBuffer->Header.DataLength[1] =
|
|||
|
(sizeof(SUB_Q_CURRENT_POSITION) - sizeof(SUB_Q_HEADER)) & 0xFF;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// we are in the thread, which alloc's/dealloc's the toc
|
|||
|
//
|
|||
|
|
|||
|
toc = DeviceExtension->CDRom.Toc;
|
|||
|
ASSERT(toc);
|
|||
|
|
|||
|
//
|
|||
|
// we return the last played sector as a result per the spec
|
|||
|
//
|
|||
|
|
|||
|
instantLba = DeviceExtension->CDRom.FinishedStreaming;
|
|||
|
|
|||
|
trackNumber = WhichTrackContainsThisLBA(toc, instantLba);
|
|||
|
|
|||
|
|
|||
|
ASSERT(trackNumber >= 0);
|
|||
|
|
|||
|
outputBuffer->FormatCode = IOCTL_CDROM_CURRENT_POSITION;
|
|||
|
outputBuffer->Control = toc->TrackData[trackNumber].Control;
|
|||
|
outputBuffer->ADR = toc->TrackData[trackNumber].Adr;
|
|||
|
outputBuffer->TrackNumber = toc->TrackData[trackNumber].TrackNumber;
|
|||
|
|
|||
|
//
|
|||
|
// Get the track's LBA
|
|||
|
//
|
|||
|
|
|||
|
lbaTrack = MSF_TO_LBA(toc->TrackData[trackNumber].Address[1],
|
|||
|
toc->TrackData[trackNumber].Address[2],
|
|||
|
toc->TrackData[trackNumber].Address[3]);
|
|||
|
|
|||
|
//
|
|||
|
// Get the current play LBA
|
|||
|
//
|
|||
|
|
|||
|
lbaRelative = instantLba;
|
|||
|
|
|||
|
//
|
|||
|
// Subtract the track's LBA to get the relative LBA
|
|||
|
//
|
|||
|
|
|||
|
lbaRelative -= lbaTrack;
|
|||
|
|
|||
|
//
|
|||
|
// Finally convert it back to MSF
|
|||
|
//
|
|||
|
|
|||
|
LBA_TO_MSF(instantLba,
|
|||
|
timeAbsolute[0],
|
|||
|
timeAbsolute[1],
|
|||
|
timeAbsolute[2]);
|
|||
|
LBA_TO_RELATIVE_MSF(lbaRelative,
|
|||
|
timeRelative[0],
|
|||
|
timeRelative[1],
|
|||
|
timeRelative[2]);
|
|||
|
|
|||
|
outputBuffer->IndexNumber = (UCHAR)trackNumber;
|
|||
|
outputBuffer->AbsoluteAddress[0] = 0;
|
|||
|
outputBuffer->AbsoluteAddress[1] = timeAbsolute[0];
|
|||
|
outputBuffer->AbsoluteAddress[2] = timeAbsolute[1];
|
|||
|
outputBuffer->AbsoluteAddress[3] = timeAbsolute[2];
|
|||
|
|
|||
|
outputBuffer->TrackRelativeAddress[0] = 0;
|
|||
|
outputBuffer->TrackRelativeAddress[1] = timeRelative[0];
|
|||
|
outputBuffer->TrackRelativeAddress[2] = timeRelative[1];
|
|||
|
outputBuffer->TrackRelativeAddress[3] = timeRelative[2];
|
|||
|
|
|||
|
//
|
|||
|
// The one line debug info...
|
|||
|
//
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctlV, "[redbook] "
|
|||
|
"ReadQ => "
|
|||
|
"Trk [%#02x] Indx [%#02x] "
|
|||
|
"Abs [%#02d:%#02d.%#02d] Rel [%#02d:%#02d.%#02d]\n",
|
|||
|
outputBuffer->TrackNumber,
|
|||
|
trackNumber,
|
|||
|
timeAbsolute[0], timeAbsolute[1], timeAbsolute[2],
|
|||
|
timeRelative[0], timeRelative[1], timeRelative[2]));
|
|||
|
|
|||
|
}
|
|||
|
//
|
|||
|
// Complete the Irp
|
|||
|
//
|
|||
|
|
|||
|
Context->Irp->IoStatus.Information = sizeof(SUB_Q_CURRENT_POSITION);
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCResume(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCResume => Not Playing\n"));
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, TRUE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCResume => Entering\n"));
|
|||
|
|
|||
|
if (TEST_FLAG(state, CD_PAUSED)) {
|
|||
|
|
|||
|
//
|
|||
|
// we need to start the resume operation
|
|||
|
//
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCResume => Resuming playback\n"));
|
|||
|
state = SetCdromState(DeviceExtension, state, CD_PLAYING);
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCResume => Resume succeeded\n"));
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// if not paused, return success
|
|||
|
//
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCResume => Not paused -- succeeded\n"));
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// always complete the Irp
|
|||
|
//
|
|||
|
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCSeek(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
same as a IOCTL_CDROM_STOP
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS status;
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCSeek => Entering\n"));
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCSeek => Not Playing\n"));
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// stop the stream if currently playing
|
|||
|
//
|
|||
|
|
|||
|
if (TEST_FLAG(state, CD_PAUSED)) {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCSeek => Paused, setting to stopped\n"));
|
|||
|
state = SetCdromState(DeviceExtension, state, CD_STOPPED);
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// since setting to a temp state, it is not appropriate to
|
|||
|
// complete the irp until the operation itself completes.
|
|||
|
//
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCSeek => stopping the stream\n"));
|
|||
|
DeviceExtension->Thread.IoctlCurrent = &Context->ListEntry;
|
|||
|
state = SetCdromState(DeviceExtension, state, state | CD_STOPPING);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCSetVolume(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PIO_STACK_LOCATION currentIrpStack;
|
|||
|
ULONG state;
|
|||
|
NTSTATUS status;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCSetVolume => Entering\n"));
|
|||
|
|
|||
|
//
|
|||
|
// guaranteed the volume info will not change right now
|
|||
|
//
|
|||
|
|
|||
|
RtlCopyMemory(&DeviceExtension->CDRom.Volume, // to
|
|||
|
Context->Irp->AssociatedIrp.SystemBuffer, // from
|
|||
|
sizeof(VOLUME_CONTROL));
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCSetVolume => Not Playing\n"));
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, TRUE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// not set above since don't have volume control
|
|||
|
//
|
|||
|
|
|||
|
RedBookKsSetVolume(DeviceExtension);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctlV, "[redbook] "
|
|||
|
"DCSetVolume => volume set to:"
|
|||
|
" (hex) %2x %2x %2x %2x\n",
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[0],
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[1],
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[2],
|
|||
|
DeviceExtension->CDRom.Volume.PortVolume[3]));
|
|||
|
|
|||
|
//
|
|||
|
// Complete the Irp (IoStatus.Information set above)
|
|||
|
//
|
|||
|
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookDCStop(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension,
|
|||
|
PREDBOOK_THREAD_IOCTL_DATA Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS status;
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCStop => Entering %p\n", Context->Irp));
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
|
|||
|
if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCStop => Stop when already stopped\n"));
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Still playing audio. if paused, just call the stop finish routine
|
|||
|
//
|
|||
|
|
|||
|
if (TEST_FLAG(state, CD_PAUSED)) {
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCStop => Stop when paused\n"));
|
|||
|
state = SetCdromState(DeviceExtension, state, CD_STOPPED);
|
|||
|
Context->Irp->IoStatus.Information = 0;
|
|||
|
Context->Irp->IoStatus.Status = STATUS_SUCCESS;
|
|||
|
RedBookCompleteIoctl(DeviceExtension, Context, FALSE);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// since setting to a temp state, it is not appropriate to
|
|||
|
// complete the irp until the operation itself completes.
|
|||
|
//
|
|||
|
|
|||
|
KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugIoctl, "[redbook] "
|
|||
|
"DCStop => stopping the stream\n"));
|
|||
|
DeviceExtension->Thread.IoctlCurrent = &Context->ListEntry;
|
|||
|
state = SetCdromState(DeviceExtension, state, state | CD_STOPPING);
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
////////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
RedBookCheckForDiscChangeAndFreeResources(
|
|||
|
PREDBOOK_DEVICE_EXTENSION DeviceExtension
|
|||
|
)
|
|||
|
|
|||
|
//
|
|||
|
// if we've paused, and the disc has changed, don't
|
|||
|
// want to be returning stale toc info when the player
|
|||
|
// resumes playback.
|
|||
|
//
|
|||
|
|
|||
|
{
|
|||
|
PIRP irp;
|
|||
|
PIO_STACK_LOCATION irpStack;
|
|||
|
PULONG count;
|
|||
|
ULONG state;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
VerifyCalledByThread(DeviceExtension);
|
|||
|
|
|||
|
//
|
|||
|
// only do this if we are in a PAUSED or STOPPED state
|
|||
|
//
|
|||
|
|
|||
|
state = GetCdromState(DeviceExtension);
|
|||
|
if ((!TEST_FLAG(state, CD_STOPPED)) &&
|
|||
|
(!TEST_FLAG(state, CD_PAUSED))) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// resources might already be deallocated.
|
|||
|
//
|
|||
|
|
|||
|
irp = DeviceExtension->Thread.CheckVerifyIrp;
|
|||
|
if (irp == NULL) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
irpStack = IoGetCurrentIrpStackLocation(irp);
|
|||
|
|
|||
|
#if DBG
|
|||
|
{
|
|||
|
//
|
|||
|
// the irp must be setup when it's allocated. we rely on this.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(irpStack->Parameters.DeviceIoControl.InputBufferLength == 0);
|
|||
|
ASSERT(irpStack->Parameters.DeviceIoControl.OutputBufferLength ==
|
|||
|
sizeof(ULONG));
|
|||
|
ASSERT(irpStack->Parameters.DeviceIoControl.IoControlCode ==
|
|||
|
IOCTL_CDROM_CHECK_VERIFY);
|
|||
|
ASSERT(irpStack->Parameters.DeviceIoControl.Type3InputBuffer == NULL);
|
|||
|
ASSERT(irp->AssociatedIrp.SystemBuffer != NULL);
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
count = (PULONG)(irp->AssociatedIrp.SystemBuffer);
|
|||
|
*count = 0;
|
|||
|
|
|||
|
RedBookForwardIrpSynchronous(DeviceExtension, irp);
|
|||
|
|
|||
|
if (!NT_SUCCESS(irp->IoStatus.Status) ||
|
|||
|
((*count) != DeviceExtension->CDRom.CheckVerify)
|
|||
|
) {
|
|||
|
|
|||
|
//
|
|||
|
// if the count has changed set the state to STOPPED.
|
|||
|
// (old state is either STOPPED or PAUSED, so either one can
|
|||
|
// seemlessly transition to the STOPPED state without any
|
|||
|
// trouble.)
|
|||
|
//
|
|||
|
// also free currently held play resources
|
|||
|
//
|
|||
|
|
|||
|
state = SetCdromState(DeviceExtension, state, CD_STOPPED);
|
|||
|
RedBookDeallocatePlayResources(DeviceExtension);
|
|||
|
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
ULONG
|
|||
|
WhichTrackContainsThisLBA(
|
|||
|
PCDROM_TOC Toc,
|
|||
|
ULONG Lba
|
|||
|
)
|
|||
|
//
|
|||
|
// returns -1 if not found
|
|||
|
//
|
|||
|
{
|
|||
|
LONG trackNumber;
|
|||
|
UCHAR msf[3];
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
LBA_TO_MSF(Lba, msf[0], msf[1], msf[2]);
|
|||
|
|
|||
|
for (trackNumber = Toc->LastTrack - Toc->FirstTrack;
|
|||
|
trackNumber >= 0;
|
|||
|
trackNumber-- ) {
|
|||
|
|
|||
|
//
|
|||
|
// we have found the track if
|
|||
|
// Minutes is less or
|
|||
|
// Minutes is equal and Seconds is less or
|
|||
|
// Minutes and Seconds are equal Frame is less or
|
|||
|
// Minutes, Seconds, and Frame are equal
|
|||
|
//
|
|||
|
// the compiler optimizes this nicely.
|
|||
|
//
|
|||
|
|
|||
|
if (Toc->TrackData[trackNumber].Address[1] < msf[0] ) {
|
|||
|
break;
|
|||
|
} else
|
|||
|
if (Toc->TrackData[trackNumber].Address[1] == msf[0] &&
|
|||
|
Toc->TrackData[trackNumber].Address[2] < msf[1] ) {
|
|||
|
break;
|
|||
|
} else
|
|||
|
if (Toc->TrackData[trackNumber].Address[1] == msf[0] &&
|
|||
|
Toc->TrackData[trackNumber].Address[2] == msf[1] &&
|
|||
|
Toc->TrackData[trackNumber].Address[3] < msf[2] ) {
|
|||
|
break;
|
|||
|
} else
|
|||
|
if (Toc->TrackData[trackNumber].Address[1] == msf[0] &&
|
|||
|
Toc->TrackData[trackNumber].Address[2] == msf[1] &&
|
|||
|
Toc->TrackData[trackNumber].Address[3] == msf[2] ) {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return trackNumber;
|
|||
|
}
|
|||
|
|
|||
|
|