mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
662 lines
14 KiB
662 lines
14 KiB
/*++
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
control.c
|
|
|
|
Abstract:
|
|
|
|
|
|
Author:
|
|
|
|
Ken Reneris (kenr) March-13-1885
|
|
|
|
Environment:
|
|
|
|
Kernel mode only.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "pciport.h"
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE,PcipControlWorker)
|
|
#pragma alloc_text(PAGE,PciCtlEject)
|
|
#pragma alloc_text(PAGE,PciCtlLock)
|
|
#pragma alloc_text(PAGE,PciCtlForward)
|
|
#pragma alloc_text(PAGE,PcipCompleteDeviceControl)
|
|
#endif
|
|
|
|
|
|
VOID
|
|
PcipStartWorker (
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used to verify a worker thread is dispatched
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ULONG WorkerQueued;
|
|
|
|
if (!PcipWorkerQueued) {
|
|
WorkerQueued = ExInterlockedExchangeUlong (
|
|
&PcipWorkerQueued,
|
|
1,
|
|
PcipSpinLock
|
|
);
|
|
|
|
if (!WorkerQueued) {
|
|
ExQueueWorkItem (&PcipWorkItem, DelayedWorkQueue);
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
PcipQueueCheckBus (
|
|
PBUS_HANDLER Handler
|
|
)
|
|
{
|
|
PPCI_PORT PciPort;
|
|
|
|
PciPort = PCIPORTDATA(Handler);
|
|
ExInterlockedInsertTailList (
|
|
&PcipCheckBusList,
|
|
&PciPort->CheckBus,
|
|
&PcipSpinlock
|
|
);
|
|
|
|
PcipStartWorker();
|
|
}
|
|
|
|
|
|
VOID
|
|
PcipControlWorker (
|
|
IN PVOID WorkerContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called by a system worker thread.
|
|
|
|
The worker thread dequeues any DeviceControls which need to be
|
|
processed and dispatches them.
|
|
|
|
It then checks for any
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY Entry;
|
|
PPCI_PORT PciPort;
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context;
|
|
|
|
PAGED_CODE ();
|
|
|
|
//
|
|
// Dispatch pending slot controls
|
|
//
|
|
|
|
for (; ;) {
|
|
Entry = ExInterlockedRemoveHeadList (
|
|
&PcipControlWorkerList,
|
|
&PcipSpinlock
|
|
);
|
|
|
|
if (!Entry) {
|
|
break;
|
|
}
|
|
|
|
Context = CONTAINING_RECORD (
|
|
Entry,
|
|
HAL_DEVICE_CONTROL_CONTEXT,
|
|
ContextWorkQueue,
|
|
);
|
|
|
|
PcipDispatchControl (Context);
|
|
}
|
|
|
|
//
|
|
// Reset worker item for next time
|
|
//
|
|
|
|
ExInitializeWorkItem (&PcipWorkItem, PcipControlWorker, NULL);
|
|
ExInterlockedExchangeUlong (&PcipWorkerQueued, 0, PcipSpinLock);
|
|
|
|
//
|
|
// Process check buses
|
|
//
|
|
|
|
for (; ;) {
|
|
Entry = ExInterlockedRemoveHeadList (
|
|
&PcipCheckBusList,
|
|
&PcipSpinlock
|
|
);
|
|
|
|
if (!Entry) {
|
|
break;
|
|
}
|
|
|
|
PciPort = CONTAINING_RECORD (
|
|
Entry,
|
|
PCI_PORT,
|
|
CheckBus
|
|
);
|
|
|
|
PcipCheckBus (PciPort, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
PcipDispatchControl (
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function dispatches a DeviceControl to the appropiate handler.
|
|
If the slot is busy, the DeviceControl may be queued for dispatching at
|
|
a later time
|
|
|
|
Arguments:
|
|
|
|
Context - The DeviceControl context to dispatch
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_CONTROL_HANDLER DeviceControlHandler;
|
|
PDEVICE_DATA DeviceData;
|
|
PPCI_PORT PciPort;
|
|
KIRQL OldIrql;
|
|
BOOLEAN EnqueueIt;
|
|
PLIST_ENTRY Link;
|
|
|
|
DeviceControlHandler = (PDEVICE_CONTROL_HANDLER) Context->ContextControlHandler;
|
|
PciPort = PCIPORTDATA(Context->Handler);
|
|
DeviceData = DeviceHandler2DeviceData (Context->DeviceControl.DeviceHandler);
|
|
|
|
//
|
|
// Get access to the device specific data.
|
|
//
|
|
|
|
KeAcquireSpinLock (&PcipSpinlock, &OldIrql);
|
|
|
|
//
|
|
// Verify the device data is still valid
|
|
//
|
|
|
|
if (!DeviceData->Valid) {
|
|
|
|
//
|
|
// Caller has invalid handle, or handle to a different device
|
|
//
|
|
|
|
DebugPrint ((2, "PCI: DeviceControl has invalid device handler \n" ));
|
|
Context->DeviceControl.Status = STATUS_NO_SUCH_DEVICE;
|
|
KeReleaseSpinLock (&PcipSpinlock, OldIrql);
|
|
HalCompleteDeviceControl (Context);
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// Check to see if this request can be begun now
|
|
//
|
|
|
|
Link = (PLIST_ENTRY) &Context->ContextWorkQueue;
|
|
EnqueueIt = DeviceControlHandler->BeginDeviceControl (DeviceData, Context);
|
|
|
|
if (EnqueueIt) {
|
|
|
|
//
|
|
// Enqueue this command to be handled when the slot is no longer busy.
|
|
//
|
|
|
|
InsertTailList (&PciPort->DeviceControl, Link);
|
|
KeReleaseSpinLock (&PcipSpinlock, OldIrql);
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// Dispatch the function to it's handler
|
|
//
|
|
|
|
KeReleaseSpinLock (&PcipSpinlock, OldIrql);
|
|
DeviceControlHandler->ControlHandler (DeviceData, Context);
|
|
}
|
|
|
|
VOID
|
|
PcipiCompleteDeviceControl (
|
|
NTSTATUS Status,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context,
|
|
PDEVICE_DATA DeviceData,
|
|
PBOOLEAN Sync
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used to complete a DeviceControl. If another DeviceControl
|
|
was delayed on this device, this function will dispatch them
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
PPCI_PORT PciPort;
|
|
PLIST_ENTRY Link;
|
|
PBOOLEAN BusyFlag;
|
|
BOOLEAN StartWorker;
|
|
PDEVICE_HANDLER_OBJECT DeviceHandler;
|
|
|
|
|
|
DeviceHandler = DeviceData2DeviceHandler(DeviceData);
|
|
BusyFlag = (PBOOLEAN) Context->ContextBusyFlag;
|
|
PciPort = PCIPORTDATA(Context->Handler);
|
|
|
|
//
|
|
// Pass it to the hal for completion
|
|
//
|
|
|
|
Context->DeviceControl.Status = Status;
|
|
HalCompleteDeviceControl (Context);
|
|
StartWorker = FALSE;
|
|
|
|
//
|
|
// Get access to the slot specific data.
|
|
//
|
|
|
|
KeAcquireSpinLock (&PcipSpinlock, &OldIrql);
|
|
|
|
//
|
|
// Clear appropiate busy flag
|
|
//
|
|
|
|
*BusyFlag = FALSE;
|
|
|
|
//
|
|
// Check to see if there are any pending slot controls for
|
|
// this device. If so, requeue them to the worker thread.
|
|
// (yes, this code is not efficient, but doing it this way
|
|
// saves a little on the nonpaged pool device_data structure size)
|
|
//
|
|
|
|
for (Link = PciPort->DeviceControl.Flink; Link != &PciPort->DeviceControl; Link = Link->Flink) {
|
|
Context = CONTAINING_RECORD (Link, HAL_DEVICE_CONTROL_CONTEXT, ContextWorkQueue);
|
|
if (Context->DeviceControl.DeviceHandler == DeviceHandler) {
|
|
RemoveEntryList (Link);
|
|
InsertTailList (&PcipControlWorkerList, Link);
|
|
StartWorker = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLock (&PcipSpinlock, OldIrql);
|
|
|
|
if (StartWorker) {
|
|
PcipStartWorker ();
|
|
}
|
|
}
|
|
|
|
VOID
|
|
PcipCompleteDeviceControl (
|
|
NTSTATUS Status,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context,
|
|
PDEVICE_DATA DeviceData
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used to complete a DeviceControl. If another DeviceControl
|
|
was delayed on this device, this function will dispatch them
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PcipiCompleteDeviceControl (
|
|
Status,
|
|
Context,
|
|
DeviceData,
|
|
&DeviceData->SyncBusy
|
|
);
|
|
}
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
PciBCtlPower (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
if ( *((PPOWER_STATE) Context->DeviceControl.Buffer) == PowerUp ) {
|
|
|
|
//
|
|
// This is a power on, there can only be one of these on the
|
|
// slot at any one time
|
|
//
|
|
|
|
ASSERT (DeviceData->AsyncBusy == FALSE);
|
|
|
|
//
|
|
// If PowerUp needs to pend, then queue the request
|
|
//
|
|
|
|
if (DeviceData->PendPowerUp) {
|
|
return TRUE;
|
|
}
|
|
|
|
DeviceData->AsyncBusy = TRUE;
|
|
Context->ContextBusyFlag = (ULONG) &DeviceData->AsyncBusy;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Something other then a PowerUp. Some form of power down,
|
|
// treat it like any other sync request
|
|
//
|
|
|
|
return PciBCtlSync (DeviceData, Context);
|
|
}
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
PciBCtlSync (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
//
|
|
// This is a sync command, verify the slot is not busy with a different
|
|
// command.
|
|
//
|
|
|
|
if (DeviceData->SyncBusy || DeviceData->AsyncBusy) {
|
|
|
|
//
|
|
// Enqueue this command to be handled when the slot is no longer busy.
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Don't enqueue, dispatch it now
|
|
//
|
|
|
|
DeviceData->SyncBusy = TRUE;
|
|
Context->ContextBusyFlag = (ULONG) &DeviceData->SyncBusy;
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
PciBCtlEject (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
BOOLEAN Busy;
|
|
|
|
//
|
|
// If Slot is busy, then wait
|
|
//
|
|
|
|
Busy = PciBCtlSync (DeviceData, Context);
|
|
|
|
if (!Busy) {
|
|
|
|
//
|
|
// Just trying to eject a device will invalidate the current
|
|
// device handler object for it...
|
|
//
|
|
|
|
DeviceData->Valid = FALSE;
|
|
DebugPrint ((5, "PCI: set handle invalid - slot %x\n", DeviceDataSlot(DeviceData)));
|
|
}
|
|
|
|
return Busy;
|
|
}
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
PciBCtlLock (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
BOOLEAN Busy;
|
|
|
|
//
|
|
// If Slot is busy, then wait
|
|
//
|
|
|
|
Busy = PciBCtlSync (DeviceData, Context);
|
|
if (!Busy &&
|
|
((PBCTL_SET_CONTROL) Context->DeviceControl.Buffer) ) {
|
|
|
|
//
|
|
// About to perform an unlock, set PendPowerUp
|
|
// This will stop any async power up requests
|
|
//
|
|
|
|
ASSERT (DeviceData->PendPowerUp == FALSE);
|
|
DeviceData->PendPowerUp = TRUE;
|
|
}
|
|
|
|
return Busy;
|
|
}
|
|
|
|
VOID
|
|
PciCtlEject (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// We don't know how to lock or unlock, but we track various
|
|
// device state. Event attempting to ejecting a device effectively
|
|
// unlocks & powers it down.
|
|
//
|
|
|
|
DeviceData->Locked = FALSE;
|
|
Status = PcipPowerDownSlot (Context->Handler, DeviceData);
|
|
|
|
//
|
|
// Pass the eject request
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = BugBugSubclass ();
|
|
}
|
|
|
|
DebugPrint ((2, "PCI: Eject complete - Slot %x, Status %x\n",
|
|
DeviceDataSlot(DeviceData), Status));
|
|
|
|
PcipCompleteDeviceControl (Status, Context, DeviceData);
|
|
}
|
|
|
|
|
|
VOID
|
|
PciCtlLock (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
BOOLEAN Lock;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// We don't know how to lock or unlock, but we track the
|
|
// device's locked state
|
|
//
|
|
|
|
Lock = ((PBCTL_SET_CONTROL) Context->DeviceControl.Buffer)->Control;
|
|
|
|
//
|
|
// If this is an unlock request, powered down the slot
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
if (!Lock) {
|
|
Status = PcipPowerDownSlot (Context->Handler, DeviceData);
|
|
}
|
|
|
|
//
|
|
// Pass the lock request to miniport driver to lock/unlock the slot
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = BugBugSubclass ();
|
|
}
|
|
|
|
//
|
|
// If it worked, set the new locked state
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
DeviceData->Locked = Lock;
|
|
}
|
|
|
|
//
|
|
// Allow power requests to continue
|
|
//
|
|
|
|
DeviceData->PendPowerUp = FALSE;
|
|
DebugPrint ((2, "PCI: %s complete - Slot %x, Status %x\n",
|
|
Lock ? "Lock" : "Unlock", DeviceDataSlot(DeviceData), Status));
|
|
|
|
PcipCompleteDeviceControl (Status, Context, DeviceData);
|
|
}
|
|
|
|
VOID
|
|
PciCtlPower (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
POWER_STATE Power;
|
|
NTSTATUS Status;
|
|
|
|
// not pagable
|
|
|
|
//
|
|
// We don't know how to power on or off the device, but we track the
|
|
// device's power state
|
|
//
|
|
|
|
Power = *((PPOWER_STATE) Context->DeviceControl.Buffer);
|
|
|
|
//
|
|
// If this is a power down request, complete it
|
|
//
|
|
|
|
if (Power != PowerUp) {
|
|
Status = PcipPowerDownSlot (Context->Handler, DeviceData);
|
|
PcipCompleteDeviceControl (Status, Context, DeviceData);
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// Device must be locked, or the power up request should have
|
|
// received an invalid device error.
|
|
//
|
|
|
|
ASSERT (DeviceData->Locked);
|
|
|
|
//
|
|
// If the device already has power, then there's nothing to do
|
|
//
|
|
|
|
if (DeviceData->Power) {
|
|
PcipiCompleteDeviceControl (STATUS_SUCCESS, Context, DeviceData, &DeviceData->AsyncBusy);
|
|
return ;
|
|
}
|
|
|
|
// bugbug pass it to a child driver, for now just complete it
|
|
|
|
PcipCompletePowerUp (DeviceData, Context);
|
|
}
|
|
|
|
VOID
|
|
PcipCompletePowerUp (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Put the device's prior configuration back.
|
|
//
|
|
|
|
DeviceData->Power = TRUE;
|
|
Status = PcipFlushConfig (Context->Handler, DeviceData);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// The device's state could not be restored. The decodes
|
|
// for the device shouldn't be enabled, so there's no immidiate
|
|
// problem. But break the current handle to the device and
|
|
// kick off a bus check. This will cause us to assign the
|
|
// device a new handle, and to power it off.
|
|
//
|
|
|
|
DeviceData->Valid = FALSE;
|
|
PcipQueueCheckBus (Context->Handler);
|
|
}
|
|
|
|
if (Context->DeviceControl.ControlCode == BCTL_SET_POWER) {
|
|
DebugPrint ((2, "PCI: Powerup complete - Slot %x, Status %x\n",
|
|
DeviceDataSlot(DeviceData), Status));
|
|
|
|
PcipiCompleteDeviceControl (Status, Context, DeviceData, &DeviceData->AsyncBusy);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
PciCtlForward (
|
|
PDEVICE_DATA DeviceData,
|
|
PHAL_DEVICE_CONTROL_CONTEXT Context
|
|
)
|
|
{
|
|
PAGED_CODE ();
|
|
|
|
DbgPrint ("PCI: BUGBUG Forward\n");
|
|
PcipCompleteDeviceControl (STATUS_NOT_IMPLEMENTED, Context, DeviceData);
|
|
}
|