/*++ Copyright (c) 1990-2000 Microsoft Corporation Module Name: pnp.c Abstract: This is the pnp portion of the video port driver. Environment: kernel mode only Revision History: --*/ #include "videoprt.h" #pragma alloc_text(PAGE,pVideoPortQueryACPIInterface) #pragma alloc_text(PAGE,pVideoPortACPIEventHandler) #pragma alloc_text(PAGE,pVideoPortACPIIoctl) #pragma alloc_text(PAGE,VpRegisterLCDCallbacks) #pragma alloc_text(PAGE,VpUnregisterLCDCallbacks) #pragma alloc_text(PAGE,VpRegisterPowerStateCallback) #pragma alloc_text(PAGE,VpDelayedPowerStateCallback) #pragma alloc_text(PAGE,VpSetLCDPowerUsage) BOOLEAN pCheckDeviceRelations( PFDO_EXTENSION FdoExtension, BOOLEAN bNewMonitor ); NTSTATUS pVideoPortQueryACPIInterface( PDEVICE_SPECIFIC_EXTENSION DoSpecificExtension ) /*++ Routine Description: Send a QueryInterface Irp to our parent (the PCI bus driver) to retrieve the AGP_BUS_INTERFACE. Returns: NT_STATUS code --*/ { KEVENT Event; PIRP QueryIrp = NULL; IO_STATUS_BLOCK IoStatusBlock; PIO_STACK_LOCATION NextStack; NTSTATUS Status; ACPI_INTERFACE_STANDARD AcpiInterface; PFDO_EXTENSION FdoExtension = DoSpecificExtension->pFdoExtension; // // For those special cases, don't use ACPI HotKey switching // if (VpSetupTypeAtBoot != SETUPTYPE_NONE) { return STATUS_INVALID_PARAMETER; } if (FdoExtension->Flags & LEGACY_DETECT) { return STATUS_INVALID_PARAMETER; } if ((FdoExtension->Flags & FINDADAPTER_SUCCEEDED) == 0) { return STATUS_INVALID_PARAMETER; } KeInitializeEvent(&Event, SynchronizationEvent, FALSE); QueryIrp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, FdoExtension->AttachedDeviceObject, NULL, 0, NULL, &Event, &IoStatusBlock); if (QueryIrp == NULL) { return STATUS_INVALID_PARAMETER; } // // Set the default error code. // QueryIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; // // Set up for a QueryInterface Irp. // NextStack = IoGetNextIrpStackLocation(QueryIrp); NextStack->MajorFunction = IRP_MJ_PNP; NextStack->MinorFunction = IRP_MN_QUERY_INTERFACE; NextStack->Parameters.QueryInterface.InterfaceType = &GUID_ACPI_INTERFACE_STANDARD; NextStack->Parameters.QueryInterface.Size = sizeof(ACPI_INTERFACE_STANDARD); NextStack->Parameters.QueryInterface.Version = 1; NextStack->Parameters.QueryInterface.Interface = (PINTERFACE) &AcpiInterface; NextStack->Parameters.QueryInterface.InterfaceSpecificData = NULL; AcpiInterface.Size = sizeof(ACPI_INTERFACE_STANDARD); AcpiInterface.Version = 1; // // Call the filter driver. // Status = IoCallDriver(FdoExtension->AttachedDeviceObject, QueryIrp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); Status = IoStatusBlock.Status; } if (NT_SUCCESS(Status)) { pVideoDebugPrint((0, "VideoPort: This is an ACPI Machine !\n")); // // Let's register for this event and provide our default handler. // AcpiInterface.RegisterForDeviceNotifications(AcpiInterface.Context, //FdoExtension->AttachedDeviceObject, pVideoPortACPIEventCallback, DoSpecificExtension); // // Register for LCD notifications // VpRegisterLCDCallbacks(); } // // Turn on HotKey switching notify mode // if (NT_SUCCESS(Status)) { ULONG active = 0; UCHAR outputBuffer[sizeof(ACPI_EVAL_OUTPUT_BUFFER) + 10]; Status = pVideoPortACPIIoctl(FdoExtension->AttachedDeviceObject, (ULONG) ('SOD_'), &active, NULL, 0, (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer); } // // Register Dock/Undock notification // if (NT_SUCCESS(Status)) { Status = IoRegisterPlugPlayNotification(EventCategoryHardwareProfileChange, 0, NULL, FdoExtension->FunctionalDeviceObject->DriverObject, pVideoPortDockEventCallback, DoSpecificExtension, &DockCallbackHandle); } return Status; } NTSTATUS pVideoPortDockEventCallback ( PVOID NotificationStructure, PDEVICE_SPECIFIC_EXTENSION DoSpecificExtension ) { UNREFERENCED_PARAMETER(NotificationStructure); pVideoPortACPIEventCallback(DoSpecificExtension, 0x77); return STATUS_SUCCESS; } VOID pVideoPortACPIEventCallback( PDEVICE_SPECIFIC_EXTENSION DoSpecificExtension, ULONG eventID ) /*++ Routine Description: Event notification callback for panel switching NOTE This routine is not pageable as it is called from DPC level by the ACPI BIOS. --*/ { PVIDEO_ACPI_EVENT_CONTEXT pContext; // // There are some cases the BIOS send the notofication even before the device is opened // if (!DoSpecificExtension->DeviceOpened) return; if (InterlockedIncrement(&(DoSpecificExtension->AcpiVideoEventsOutstanding)) < 2) { // Queue work item pContext = ExAllocatePoolWithTag(NonPagedPool, sizeof(VIDEO_ACPI_EVENT_CONTEXT), VP_TAG); if (pContext && (eventID == 0x80 || eventID == 0x81 || eventID == 0x90 || eventID == 0x77)) { pContext->DoSpecificExtension = DoSpecificExtension; pContext->EventID = eventID; ExInitializeWorkItem(&(pContext->workItem), pVideoPortACPIEventHandler, pContext); ExQueueWorkItem(&(pContext->workItem), DelayedWorkQueue); } } else { // We're getting a Notify storm, and we already have a work item on the job. InterlockedDecrement(&(DoSpecificExtension->AcpiVideoEventsOutstanding)); } return; } VOID pVideoPortACPIEventHandler( PVIDEO_ACPI_EVENT_CONTEXT EventContext ) /*++ Routine Description: Event handler for panel switching --*/ { UCHAR outputBuffer[0x200 + sizeof(ACPI_EVAL_OUTPUT_BUFFER)]; PCHILD_PDO_EXTENSION pChildDeviceExtension; PDEVICE_OBJECT AttachedDeviceObject; PDEVICE_OBJECT pChildPdos[10]; ULONG active, szChildIDs, i, AllowSwitch = 0, Switched = 0; PVIDEO_CHILD_STATE_CONFIGURATION pChildIDs; NTSTATUS Status; BOOLEAN bNewMonitor; VIDEO_WIN32K_CALLBACKS_PARAMS calloutParams; PFDO_EXTENSION FdoExtension; FdoExtension = EventContext->DoSpecificExtension->pFdoExtension; ASSERT (FdoExtension != NULL); pVideoDebugPrint((1, "pVideoPortACPIEventHandler: Event %08lx trigerred!\n", EventContext->EventID)); AttachedDeviceObject = FdoExtension->AttachedDeviceObject; if (FdoExtension->DevicePowerState != PowerDeviceD0) { EventContext->DoSpecificExtension->CachedEventID = EventContext->EventID; goto ExitACPIEventHandler; } else { EventContext->DoSpecificExtension->CachedEventID = 0; } // // Dock/Undock event handling // if (EventContext->EventID == 0x77) { calloutParams.CalloutType = VideoDisplaySwitchCallout; calloutParams.PhysDisp = EventContext->DoSpecificExtension->PhysDisp; calloutParams.Param = (ULONG_PTR)NULL; VpWin32kCallout(&calloutParams); goto ExitACPIEventHandler; } // // Disable BIOS notification // active = 2; pVideoPortACPIIoctl(AttachedDeviceObject, (ULONG) ('SOD_'), &active, NULL, 0, (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer); if (EventContext->EventID == 0x90) { calloutParams.CalloutType = VideoWakeupCallout; VpWin32kCallout(&calloutParams); } else { szChildIDs = sizeof(VIDEO_CHILD_STATE_CONFIGURATION) + FdoExtension->ChildPdoNumber*sizeof(VIDEO_CHILD_STATE); pChildIDs = (PVIDEO_CHILD_STATE_CONFIGURATION)ExAllocatePoolWithTag(PagedPool, szChildIDs, VP_TAG); if (pChildIDs != NULL) { // // During switching, no PnP action is allowed // ACQUIRE_DEVICE_LOCK (FdoExtension); pChildIDs->Count = 0; for (pChildDeviceExtension = FdoExtension->ChildPdoList; pChildDeviceExtension != NULL; pChildDeviceExtension = pChildDeviceExtension->NextChild ) { if ((!pChildDeviceExtension->bIsEnumerated) || pChildDeviceExtension->VideoChildDescriptor->Type != Monitor) { continue; } pChildIDs->ChildStateArray[pChildIDs->Count].Id = pChildDeviceExtension->VideoChildDescriptor->UId; pChildIDs->ChildStateArray[pChildIDs->Count].State = 0; pChildPdos[pChildIDs->Count] = pChildDeviceExtension->ChildDeviceObject; Status = pVideoPortACPIIoctl( IoGetAttachedDevice(pChildDeviceExtension->ChildDeviceObject), (ULONG) ('SGD_'), NULL, NULL, sizeof(ACPI_EVAL_OUTPUT_BUFFER)+0x10, (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer); if (NT_SUCCESS(Status)) { ASSERT(((PACPI_EVAL_OUTPUT_BUFFER)outputBuffer)->Argument[0].Type == ACPI_METHOD_ARGUMENT_INTEGER); ASSERT(((PACPI_EVAL_OUTPUT_BUFFER)outputBuffer)->Argument[0].DataLength == sizeof(ULONG)); if (((PACPI_EVAL_OUTPUT_BUFFER)outputBuffer)->Argument[0].Argument) { pChildIDs->ChildStateArray[pChildIDs->Count].State = 1; } } pChildIDs->Count++; } szChildIDs = sizeof(VIDEO_CHILD_STATE_CONFIGURATION) + pChildIDs->Count*sizeof(VIDEO_CHILD_STATE); // // Notify Miniport that display switching is about to happen. // Treat the switch is allowed by default. // AllowSwitch = 1; pVideoMiniDeviceIoControl(FdoExtension->FunctionalDeviceObject, IOCTL_VIDEO_VALIDATE_CHILD_STATE_CONFIGURATION, (PVOID)pChildIDs, szChildIDs, &AllowSwitch, sizeof(ULONG)); // // If Miniport says it's OK to proceed // if (AllowSwitch != 0) { // // Check the Miniport do the switching for us // Status = pVideoMiniDeviceIoControl(FdoExtension->FunctionalDeviceObject, IOCTL_VIDEO_SET_CHILD_STATE_CONFIGURATION, (PVOID)pChildIDs, szChildIDs, NULL, 0); if (NT_SUCCESS(Status)) { pVideoDebugPrint((1, "VideoPort: Moniport does the switch!\n")); Switched = 1; } } // // The last _DSS needs to commit the switching // if (pChildIDs->Count > 0) { pChildIDs->ChildStateArray[pChildIDs->Count-1].State |= 0x80000000; } for (i = 0; i < pChildIDs->Count; i++) { // // If Miniport doesn't like to proceed or it does the switching already, just notify BIOS to go to next _DGS state // // Found some bad BIOS(Toshiba). They do switch anyway regardless of 0x40000000 bit. This has extremely bad effect // on DualView // if (!AllowSwitch) continue; if (Switched) { pChildIDs->ChildStateArray[i].State |= 0x40000000; } pVideoPortACPIIoctl(IoGetAttachedDevice(pChildPdos[i]), (ULONG) ('SSD_'), &pChildIDs->ChildStateArray[i].State, NULL, 0, (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer); } RELEASE_DEVICE_LOCK (FdoExtension); ExFreePool(pChildIDs); } // // On switching displays, call GDI / USER to tell the device to rebuild mode list // and change current mode if neccesary // pVideoDebugPrint((0, "VideoPrt.sys: Display switching occured - calling GDI to rebuild mode table.\n")); calloutParams.CalloutType = VideoDisplaySwitchCallout; calloutParams.PhysDisp = (AllowSwitch) ? EventContext->DoSpecificExtension->PhysDisp : NULL; // // On Monitor changing, we receive Notify(81) // On waking up from hibernation, we receive Notify(90) // We also make IoInvalidateDeviceRelation happen inside Callout routine // bNewMonitor = (EventContext->EventID == 0x81); if (pCheckDeviceRelations(FdoExtension, bNewMonitor) ) { calloutParams.Param = (ULONG_PTR)FdoExtension->PhysicalDeviceObject; } else { calloutParams.Param = (ULONG_PTR)NULL; } VpWin32kCallout(&calloutParams); } ExitACPIEventHandler: // // Reenable BIOS notification // active = 0; pVideoPortACPIIoctl(AttachedDeviceObject, (ULONG) ('SOD_'), &active, NULL, 0, (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer); InterlockedDecrement(&(EventContext->DoSpecificExtension->AcpiVideoEventsOutstanding)); // // This also ends up freeing the work item as it's embedded in the context. // ExFreePool(EventContext); return; } NTSTATUS pVideoPortACPIIoctl( IN PDEVICE_OBJECT DeviceObject, IN ULONG MethodName, IN PULONG InputParam1, IN PULONG InputParam2, IN ULONG OutputBufferSize, IN PACPI_EVAL_OUTPUT_BUFFER pOutputBuffer ) /*++ Routine Description: Called to send a request to the DeviceObject Arguments: DeviceObject - The request is sent to this device object MethodName - Name of the method to be run in ACPI space pArgumets - Pointer that will receive the address of the ACPI data Return Value: NT Status of the operation --*/ { UCHAR buffer[sizeof(ACPI_EVAL_INPUT_BUFFER_COMPLEX) + sizeof(ACPI_METHOD_ARGUMENT)]; PACPI_EVAL_INPUT_BUFFER_COMPLEX pInputBuffer; ULONG size; IO_STATUS_BLOCK ioBlock; KEVENT event; NTSTATUS status; PIRP irp; pVideoDebugPrint((2, "Call ACPI method %c%c%c%c!\n", *((PUCHAR)&MethodName), *((PUCHAR)&MethodName+1), *((PUCHAR)&MethodName+2), *((PUCHAR)&MethodName+3) )); pInputBuffer = (PACPI_EVAL_INPUT_BUFFER_COMPLEX) buffer; pInputBuffer->MethodNameAsUlong = MethodName; if (InputParam1 == NULL) { size = sizeof(ACPI_EVAL_INPUT_BUFFER); pInputBuffer->Signature = ACPI_EVAL_INPUT_BUFFER_SIGNATURE; } else { size = sizeof(ACPI_EVAL_INPUT_BUFFER_COMPLEX); pInputBuffer->Signature = ACPI_EVAL_INPUT_BUFFER_COMPLEX_SIGNATURE; pInputBuffer->Size = sizeof(ACPI_METHOD_ARGUMENT); pInputBuffer->ArgumentCount = 1; pInputBuffer->Argument[0].Type = ACPI_METHOD_ARGUMENT_INTEGER; pInputBuffer->Argument[0].DataLength = sizeof(ULONG); pInputBuffer->Argument[0].Argument = *InputParam1; } if (InputParam2) { size = sizeof(ACPI_EVAL_INPUT_BUFFER_COMPLEX) + sizeof(ACPI_METHOD_ARGUMENT); pInputBuffer->Size = 2 * sizeof(ACPI_METHOD_ARGUMENT); pInputBuffer->ArgumentCount = 2; pInputBuffer->Argument[1].Type = ACPI_METHOD_ARGUMENT_INTEGER; pInputBuffer->Argument[1].DataLength = sizeof(ULONG); pInputBuffer->Argument[1].Argument = *InputParam2; } // // Initialize an event to wait on // KeInitializeEvent(&event, SynchronizationEvent, FALSE); // // Build the request // irp = IoBuildDeviceIoControlRequest(IOCTL_ACPI_EVAL_METHOD, DeviceObject, pInputBuffer, size, pOutputBuffer, OutputBufferSize, FALSE, &event, &ioBlock); if (!irp) { return STATUS_INSUFFICIENT_RESOURCES; } // // Pass request to DeviceObject, always wait for completion routine // status = IoCallDriver(DeviceObject, irp); if (status == STATUS_PENDING) { // // Wait for the irp to be completed, then grab the real status code // KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = ioBlock.Status; } // // Sanity check the data // if (NT_SUCCESS(status) && OutputBufferSize != 0) { if (((pOutputBuffer)->Signature != ACPI_EVAL_OUTPUT_BUFFER_SIGNATURE) || ((pOutputBuffer)->Count == 0)) { status = STATUS_ACPI_INVALID_DATA; } } return status; } NTSTATUS pVideoMiniDeviceIoControl( IN PDEVICE_OBJECT DeviceObject, IN ULONG dwIoControlCode, IN PVOID lpInBuffer, IN ULONG nInBufferSize, OUT PVOID lpOutBuffer, IN ULONG nOutBufferSize ) { PFDO_EXTENSION combinedExtension; PFDO_EXTENSION fdoExtension; VIDEO_REQUEST_PACKET vrp; STATUS_BLOCK statusBlock; combinedExtension = DeviceObject->DeviceExtension; fdoExtension = combinedExtension->pFdoExtension; statusBlock.Status = ERROR_INVALID_FUNCTION; vrp.IoControlCode = dwIoControlCode; vrp.StatusBlock = &statusBlock; vrp.InputBuffer = lpInBuffer; vrp.InputBufferLength = nInBufferSize; vrp.OutputBuffer = lpOutBuffer; vrp.OutputBufferLength = nOutBufferSize; // // Send the request to the miniport directly. // fdoExtension->HwStartIO(combinedExtension->HwDeviceExtension, &vrp); pVideoPortMapToNtStatus(&statusBlock); return (statusBlock.Status); } NTSTATUS VpQueryBacklightLevels( IN PDEVICE_OBJECT DeviceObject, OUT PUCHAR ucBacklightLevels, OUT PULONG pulNumberOfLevelsSupported ) /*++ Routine Description: This function will query the list of levels supported by _BCL. Arguments: DeviceObject: The ACPI device object attached to our LCD device. ucBacklightLevels: The list of backlight levels supported by the ACPI BIOS. pulNumberOfLevelsSupported: This is the number of actual levels the ACPI BIOS supports, Returns: NO_ERROR if it succeeds Various error codes if it fails --*/ { PACPI_EVAL_OUTPUT_BUFFER Buffer = NULL; PACPI_METHOD_ARGUMENT Argument = NULL; ULONG Granularity = 80; ULONG BufferMaxSize = 4096; NTSTATUS Status = STATUS_UNSUCCESSFUL; ULONG Level = 100; ULONG count = 0; PBACKLIGHT_STATUS pVpBacklightStatus = &VpBacklightStatus; PUCHAR ucLevels = ucBacklightLevels; PAGED_CODE(); ASSERT (DeviceObject != NULL); // // Get the list of brightness levels supported // do { Buffer = (PACPI_EVAL_OUTPUT_BUFFER)ExAllocatePoolWithTag( PagedPool, Granularity, VP_TAG); if (Buffer == NULL) { pVideoDebugPrint((Warn, "VIDEOPRT: VpQueryBacklightLevels: Memory allocation failed.")); Status = STATUS_INSUFFICIENT_RESOURCES; break; } RtlZeroMemory(Buffer, Granularity); Status = pVideoPortACPIIoctl( DeviceObject, (ULONG) ('LCB_'), NULL, NULL, Granularity, Buffer); if (Status == STATUS_BUFFER_OVERFLOW) { ExFreePool(Buffer); Buffer = NULL; Granularity <<= 1; if (Granularity > BufferMaxSize) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL failed. Expected buffer is too big.")); Status = STATUS_ACPI_INVALID_DATA; break; } } else if (!NT_SUCCESS(Status)) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL failed. Status = 0x%x\n", Status)); } else { pVideoDebugPrint((Trace, "VIDEOPRT: _BCL succeeded.\n")); } } while (Status == STATUS_BUFFER_OVERFLOW); if ((Buffer == NULL) || (!NT_SUCCESS(Status))) goto Fallout; // // We should have 2+ levels. If we have only have 2 levels, the // BIOS only reports the recommended AC/DC values. This function // is therefore only useful if we have 3+ levels reported by the // ACPI BIOS. // if (Buffer->Count < 3) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL returned an fewer than three arguments.")); Status = STATUS_ACPI_INVALID_DATA; goto Fallout; } // // Save off BIOS "default" AC value for initial settings. // Argument = Buffer->Argument; if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL returned an invalid argument.")); Status = STATUS_ACPI_INVALID_DATA; goto Fallout; } Level = Argument->Argument; pVpBacklightStatus->bBIOSDefaultACKnown = TRUE; pVpBacklightStatus->ucBIOSDefaultAC = (unsigned char) Level; // // Save off BIOS "default" DC value for initial settings. // Argument = ACPI_METHOD_NEXT_ARGUMENT(Argument); if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL returned an invalid argument.")); Status = STATUS_ACPI_INVALID_DATA; goto Fallout; } // // Full power level should be greater than the battery level // ASSERT (Level >= Argument->Argument); Level = Argument->Argument; pVpBacklightStatus->bBIOSDefaultDCKnown = TRUE; pVpBacklightStatus->ucBIOSDefaultDC = (unsigned char) Level; // // Run through the list of supported modes that follow the AC/DC // values and return them to the caller. // *pulNumberOfLevelsSupported = 0; for (count = 0; count < (Buffer->Count - 2); count++) { // // Proceed to the next argument // Argument = ACPI_METHOD_NEXT_ARGUMENT(Argument); if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL returned an invalid argument.")); Status = STATUS_ACPI_INVALID_DATA; goto Fallout; } // // Save off the argument in our array of levels. // Level = Argument->Argument; *ucLevels++ = (unsigned char) Level; *pulNumberOfLevelsSupported += 1; } Status = NO_ERROR; Fallout: if (Buffer != NULL) { ExFreePool(Buffer); } return Status; } NTSTATUS VpSetLCDPowerUsage( IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN FullPower ) /*++ Routine Description: It changes the brightness level, based on the value of FullPower. Arguments: DeviceObject: the ACPI device object attached to our LCD device. FullPower: if TRUE, it sets the brightness level to FullPower level if FALSE, it sets the brightness level to Battery level Returns: NO_ERROR if it succeeds Various error codes if it fails --*/ { PACPI_EVAL_OUTPUT_BUFFER Buffer = NULL; PACPI_METHOD_ARGUMENT Argument = NULL; ULONG Granularity = 80; ULONG BufferMaxSize = 4096; NTSTATUS Status = STATUS_UNSUCCESSFUL; ULONG Level = 100; PBACKLIGHT_STATUS pVpBacklightStatus = &VpBacklightStatus; PAGED_CODE(); ASSERT (DeviceObject != NULL); // // Track whether we are running on AC or DC // VpRunningOnAC = FullPower; // // If we are using the new API for backlight control, we don't need to query the // BIOS with _BCL. We will set the AC or DC level as appropriate with _BCM. // if (pVpBacklightStatus->bNewAPISupported) { if (VpRunningOnAC) { Level = (ULONG) pVpBacklightStatus->ucACBrightness; } else { Level = (ULONG) pVpBacklightStatus->ucDCBrightness; } Status = pVideoPortACPIIoctl( DeviceObject, (ULONG) ('MCB_'), &Level, NULL, 0, (PACPI_EVAL_OUTPUT_BUFFER)NULL); return Status; } // // Get the list of brightness levels supported // do { Buffer = (PACPI_EVAL_OUTPUT_BUFFER)ExAllocatePoolWithTag( PagedPool, Granularity, VP_TAG); if (Buffer == NULL) { pVideoDebugPrint((Warn, "VIDEOPRT: VpSetLCDPowerUsage: Memory allocation failed.")); Status = STATUS_INSUFFICIENT_RESOURCES; break; } RtlZeroMemory(Buffer, Granularity); Status = pVideoPortACPIIoctl( DeviceObject, (ULONG) ('LCB_'), NULL, NULL, Granularity, Buffer); if (Status == STATUS_BUFFER_OVERFLOW) { ExFreePool(Buffer); Buffer = NULL; Granularity <<= 1; if (Granularity > BufferMaxSize) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL failed. Expected buffer is too big.")); Status = STATUS_ACPI_INVALID_DATA; break; } } else if (!NT_SUCCESS(Status)) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL failed. Status = 0x%x\n", Status)); } else { pVideoDebugPrint((Trace, "VIDEOPRT: _BCL succeeded.\n")); } } while (Status == STATUS_BUFFER_OVERFLOW); if ((Buffer == NULL) || (!NT_SUCCESS(Status))) goto Fallout; // // Now try to set the state. // if (Buffer->Count < 2) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL returned an invalid number of arguments.")); Status = STATUS_ACPI_INVALID_DATA; goto Fallout; } Argument = Buffer->Argument; if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL returned an invalid argument.")); Status = STATUS_ACPI_INVALID_DATA; goto Fallout; } Level = Argument->Argument; if (!FullPower) { Argument = ACPI_METHOD_NEXT_ARGUMENT(Argument); if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCL returned an invalid argument.")); Status = STATUS_ACPI_INVALID_DATA; goto Fallout; } // // Full power level should be greater than the battery level // ASSERT (Level >= Argument->Argument); Level = Argument->Argument; } Status = pVideoPortACPIIoctl( DeviceObject, (ULONG) ('MCB_'), &Level, NULL, 0, (PACPI_EVAL_OUTPUT_BUFFER)NULL); if (!NT_SUCCESS(Status)) { pVideoDebugPrint((Warn, "VIDEOPRT: _BCM failed. Status = 0x%x\n", Status)); } else { pVideoDebugPrint((Trace, "VIDEOPRT: _BCM succeeded.\n")); } Fallout: if (Buffer != NULL) { ExFreePool(Buffer); } return Status; } VOID VpPowerStateCallback( IN PVOID CallbackContext, IN PVOID Argument1, IN PVOID Argument2 ) /*++ Routine Description: This is the callback routine that is called when the power state changes. Arguments: CallbackContext: NULL Argument1: event code Argument2: if Argument1 is PO_CB_AC_STATUS, Argument2 contains TRUE if the current power source is AC and FALSE otherwise. Note: This function can be called at DISPATCH_LEVEL --*/ { PPOWER_STATE_WORK_ITEM PowerStateWorkItem = NULL; ASSERT (CallbackContext == NULL); switch ((ULONG_PTR)Argument1) { case PO_CB_LID_SWITCH_STATE: case PO_CB_AC_STATUS: PowerStateWorkItem = (PPOWER_STATE_WORK_ITEM)ExAllocatePoolWithTag( NonPagedPool, sizeof(POWER_STATE_WORK_ITEM), VP_TAG); if (PowerStateWorkItem != NULL) { PowerStateWorkItem->Argument1 = Argument1; PowerStateWorkItem->Argument2 = Argument2; ExInitializeWorkItem( &PowerStateWorkItem->WorkItem, VpDelayedPowerStateCallback, PowerStateWorkItem); ExQueueWorkItem( &PowerStateWorkItem->WorkItem, DelayedWorkQueue); } else { pVideoDebugPrint((Warn, "VIDEOPRT: VpPowerStateCallback: Memory allocation failed.")); } break; default: // // Ignore all other cases // break; } } VOID VpDelayedPowerStateCallback( IN PVOID Context ) /*++ Routine Description: VpPowerStateCallback queues this work item in order to handle PowerState changes at PASSIVE_LEVEL. Arguments: Context: pointer to POWER_STATE_WORK_ITEM --*/ { PPOWER_STATE_WORK_ITEM PowerStateWorkItem = (PPOWER_STATE_WORK_ITEM)Context; PDEVICE_OBJECT AttachedDevice = NULL; NTSTATUS status; POWER_STATE powerState; PCHILD_PDO_EXTENSION pdoExtension; PAGED_CODE(); ASSERT (PowerStateWorkItem != NULL); KeWaitForSingleObject(&LCDPanelMutex, Executive, KernelMode, FALSE, (PTIME)NULL); if (LCDPanelDevice == NULL) { goto Fallout; } switch ((ULONG_PTR)PowerStateWorkItem->Argument1) { case PO_CB_AC_STATUS: AttachedDevice = IoGetAttachedDeviceReference(LCDPanelDevice); VpSetLCDPowerUsage( AttachedDevice, (PowerStateWorkItem->Argument2 != 0)); ObDereferenceObject(AttachedDevice); break; case PO_CB_LID_SWITCH_STATE: pdoExtension = LCDPanelDevice->DeviceExtension; if ((ULONG_PTR)PowerStateWorkItem->Argument2 == 0) { // // The lid is closed. Put the LCD Panel into D3 and override // any future power requests to the panel. // pdoExtension->PowerOverride = TRUE; powerState.DeviceState = PowerDeviceD3; pVideoDebugPrint((Trace, "VIDEOPRT: LCD Panel Closed.\n")); } else if ((ULONG_PTR)PowerStateWorkItem->Argument2 == 1) { pdoExtension->PowerOverride = FALSE; powerState.DeviceState = PowerDeviceD0; pVideoDebugPrint((Trace, "VIDEOPRT: LCD Panel Open.\n")); } else { pVideoDebugPrint((Error, "VIDEOPRT: Unknown LCD lid close event recieved.\n")); ASSERT(FALSE); goto Fallout; } if (pdoExtension->pFdoExtension->DevicePowerState == PowerDeviceD0) { if (VpLidCloseSetPower == TRUE) { status = PoRequestPowerIrp (LCDPanelDevice, IRP_MN_SET_POWER, powerState, pVideoPortPowerIrpComplete, (PVOID)NULL, NULL); if (status != STATUS_PENDING) { pVideoDebugPrint((Error, "VIDEOPRT: Could not send power IRP to toggle panel\n")); } } } break; default: pVideoDebugPrint((Warn, "VIDEOPRT: Unexpected PowerState event recieved.\n")); ASSERT(FALSE); break; } Fallout: KeReleaseMutex(&LCDPanelMutex, FALSE); ExFreePool(Context); } VOID VpRegisterPowerStateCallback( VOID ) /*++ Routine Description: This routine registers the PowerState callback routine. --*/ { OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING CallbackName; PCALLBACK_OBJECT CallbackObject; NTSTATUS Status; PAGED_CODE(); RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState"); InitializeObjectAttributes( &ObjectAttributes, &CallbackName, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, NULL ); Status = ExCreateCallback( &CallbackObject, &ObjectAttributes, FALSE, TRUE ); if (NT_SUCCESS(Status)) { PowerStateCallbackHandle = ExRegisterCallback( CallbackObject, VpPowerStateCallback, NULL ); if (PowerStateCallbackHandle == NULL) { pVideoDebugPrint((Warn, "VIDEOPRT: Could not register VpPowerStateCallback.\n")); } else { pVideoDebugPrint((Trace, "VIDEOPRT: VpPowerStateCallback registered. \n")); } } else { pVideoDebugPrint((Warn, "VIDEOPRT: Could not get the PowerState callback object.\n")); } } VOID VpRegisterLCDCallbacks( VOID ) /*++ Routine Description: This routine registers a callback with the system so that we can be notified of power state changes. --*/ { PAGED_CODE(); // // Register power state callback. This works for the lid as well. // if (PowerStateCallbackHandle == NULL) { VpRegisterPowerStateCallback(); } } VOID VpUnregisterLCDCallbacks( VOID ) /*++ Routine Description: This routine unregisters the callbacks previously registered by VpRegisterLCDCallbacks Arguments: None. Note: The global PowerStateCallbackHandle acts as an implicit parameter. --*/ { PAGED_CODE(); // // Unregister power state callback // if (PowerStateCallbackHandle != NULL) { ExUnregisterCallback(PowerStateCallbackHandle); PowerStateCallbackHandle = NULL; } }