windows-nt/Source/XPSP1/NT/base/busdrv/pci/config.c

907 lines
20 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1996-2000 Microsoft Corporation
Module Name:
config.c
Abstract:
Two kinds of config space access are allowed. One for the config space
associated with a specific PDO and one for a device specified in terms of
a (RootFdo, BusNumber, Slot) tuple.
Author:
Andrew Thornton (andrewth) 27-Aug-1998
Revision History:
--*/
#include "pcip.h"
#define INT_LINE_OFFSET ((ULONG)FIELD_OFFSET(PCI_COMMON_CONFIG,u.type0.InterruptLine))
//
// None of these functions are pageable as they are called to power manage
// devices at high IRQL
//
VOID
PciReadWriteConfigSpace(
IN PPCI_FDO_EXTENSION ParentFdo,
IN PCI_SLOT_NUMBER Slot,
IN PVOID Buffer,
IN ULONG Offset,
IN ULONG Length,
IN BOOLEAN Read
)
/*++
Routine Description:
This is the base routine through which all config space access from the
pci driver go.
Arguments:
ParentFdo - The FDO of the bus who's config space we want
Slot - The Device/Function of the device on that bus we are interested in
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Read - TRUE to read from config space, FALSE to write
Return Value:
None
Notes:
If the underlying HAL or ACPI access mechanism failes we bugcheck with a
PCI_CONFIG_SPACE_ACCESS_FAILURE
--*/
{
NTSTATUS status;
PciReadWriteConfig busHandlerReadWrite;
PCI_READ_WRITE_CONFIG interfaceReadWrite;
ULONG count;
PPCI_BUS_INTERFACE_STANDARD busInterface;
ASSERT(PCI_IS_ROOT_FDO(ParentFdo->BusRootFdoExtension));
busInterface = ParentFdo->BusRootFdoExtension->PciBusInterface;
if (busInterface) {
//
// If we have a PCI_BUS_INTERFACE use it to access config space
//
if (Read) {
interfaceReadWrite = busInterface->ReadConfig;
} else {
interfaceReadWrite = busInterface->WriteConfig;
}
//
// The interface access to config space is at the root of each PCI
// domain
//
count = interfaceReadWrite(
busInterface->Context,
ParentFdo->BaseBus,
Slot.u.AsULONG,
Buffer,
Offset,
Length
);
if (count != Length) {
KeBugCheckEx(
PCI_CONFIG_SPACE_ACCESS_FAILURE,
(ULONG_PTR) ParentFdo->BaseBus, // Bus
(ULONG_PTR) Slot.u.AsULONG, // Slot
(ULONG_PTR) Offset, // Offset
(ULONG_PTR) Read // Read/Write
);
}
} else {
//
// The BusHandler interface is at the parent level.
//
// NOTE: This means that if hot-plug of bridges (aka Docking) is to be
// supported then the HAL must provide a PCI_BUS_INTERFACE_STANDARD
// because it will not have a bus handler for the new bridge so we
// won't be able to use this code path.
//
ASSERT(ParentFdo->BusHandler);
//
// We had better not think we can do hot plug
//
ASSERT(!PciAssignBusNumbers);
if (Read) {
busHandlerReadWrite =
((PPCIBUSDATA)ParentFdo->BusHandler->BusData)->ReadConfig;
} else {
busHandlerReadWrite =
((PPCIBUSDATA)ParentFdo->BusHandler->BusData)->WriteConfig;
}
busHandlerReadWrite(ParentFdo->BusHandler,
Slot,
Buffer,
Offset,
Length
);
}
}
VOID
PciReadDeviceConfig(
IN PPCI_PDO_EXTENSION Pdo,
IN PVOID Buffer,
IN ULONG Offset,
IN ULONG Length
)
/*++
Routine Description:
Read the config space for a specific device
Arguments:
Pdo - The PDO representing the device who's config space we want
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Return Value:
None
--*/
{
PciReadWriteConfigSpace(PCI_PARENT_FDOX(Pdo),
Pdo->Slot,
Buffer,
Offset,
Length,
TRUE // read
);
}
VOID
PciWriteDeviceConfig(
IN PPCI_PDO_EXTENSION Pdo,
IN PVOID Buffer,
IN ULONG Offset,
IN ULONG Length
)
/*++
Routine Description:
Write the config space for a specific device
Arguments:
Pdo - The PDO representing the device who's config space we want
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Return Value:
None
--*/
{
#if 0
//
// Make sure we never change the interrupt line register
//
if ((Offset <= INT_LINE_OFFSET)
&& (Offset + Length > INT_LINE_OFFSET)) {
PUCHAR interruptLine = (PUCHAR)Buffer + INT_LINE_OFFSET - Offset;
ASSERT(*interruptLine == Pdo->RawInterruptLine);
}
#endif
PciReadWriteConfigSpace(PCI_PARENT_FDOX(Pdo),
Pdo->Slot,
Buffer,
Offset,
Length,
FALSE // write
);
}
VOID
PciReadSlotConfig(
IN PPCI_FDO_EXTENSION ParentFdo,
IN PCI_SLOT_NUMBER Slot,
IN PVOID Buffer,
IN ULONG Offset,
IN ULONG Length
)
/*++
Routine Description:
Read config space for a specific bus/slot
Arguments:
ParentFdo - The FDO of the bus who's config space we want
Slot - The Device/Function of the device on that bus we are interested in
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Return Value:
None
--*/
{
PciReadWriteConfigSpace(ParentFdo,
Slot,
Buffer,
Offset,
Length,
TRUE // read
);
}
VOID
PciWriteSlotConfig(
IN PPCI_FDO_EXTENSION ParentFdo,
IN PCI_SLOT_NUMBER Slot,
IN PVOID Buffer,
IN ULONG Offset,
IN ULONG Length
)
/*++
Routine Description:
Read config space for a specific bus/slot
Arguments:
ParentFdo - The FDO of the bus who's config space we want
Slot - The Device/Function of the device on that bus we are interested in
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Return Value:
None
--*/
{
PciReadWriteConfigSpace(ParentFdo,
Slot,
Buffer,
Offset,
Length,
FALSE // write
);
}
UCHAR
PciGetAdjustedInterruptLine(
IN PPCI_PDO_EXTENSION Pdo
)
/*++
Routine Description:
This updates the interrupt line the HAL would like the world to see - this
may or may not be different than the raw pin.
Arguments:
Pdo - The PDO representing the device who's config space we want
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Return Value:
None
--*/
{
UCHAR adjustedInterruptLine = 0;
ULONG lengthRead;
//
// Just in case anyone messes up the structures
//
ASSERT(INT_LINE_OFFSET
== (ULONG)FIELD_OFFSET(PCI_COMMON_CONFIG, u.type1.InterruptLine));
ASSERT(INT_LINE_OFFSET
== (ULONG)FIELD_OFFSET(PCI_COMMON_CONFIG, u.type2.InterruptLine));
if (Pdo->InterruptPin != 0) {
//
// Get the adjusted line the HAL wants us to see
//
lengthRead = HalGetBusDataByOffset(
PCIConfiguration,
PCI_PARENT_FDOX(Pdo)->BaseBus,
Pdo->Slot.u.AsULONG,
&adjustedInterruptLine,
INT_LINE_OFFSET,
sizeof(adjustedInterruptLine));
if (lengthRead != sizeof(adjustedInterruptLine)) {
adjustedInterruptLine = Pdo->RawInterruptLine;
}
}
return adjustedInterruptLine;
}
NTSTATUS
PciQueryForPciBusInterface(
IN PPCI_FDO_EXTENSION FdoExtension
)
/*++
Routine Description:
This routine sends an IRP to the parent PDO requesting
handlers for PCI configuration reads and writes.
Arguments:
FdoExtension - this PCI bus's FDO extension
Return Value:
STATUS_SUCCESS, if the PDO provided handlers
Notes:
--*/
{
NTSTATUS status;
PPCI_BUS_INTERFACE_STANDARD interface;
PDEVICE_OBJECT targetDevice = NULL;
KEVENT irpCompleted;
IO_STATUS_BLOCK statusBlock;
PIRP irp = NULL;
PIO_STACK_LOCATION irpStack;
PAGED_CODE();
//
// We only do this for root busses
//
ASSERT(PCI_IS_ROOT_FDO(FdoExtension));
interface = ExAllocatePool(NonPagedPool, sizeof(PCI_BUS_INTERFACE_STANDARD));
if (!interface) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Find out where we are sending the irp
//
targetDevice = IoGetAttachedDeviceReference(FdoExtension->PhysicalDeviceObject);
//
// Get an IRP
//
KeInitializeEvent(&irpCompleted, SynchronizationEvent, FALSE);
irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP,
targetDevice,
NULL, // Buffer
0, // Length
0, // StartingOffset
&irpCompleted,
&statusBlock
);
if (!irp) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto cleanup;
}
irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
irp->IoStatus.Information = 0;
//
// Initialize the stack location
//
irpStack = IoGetNextIrpStackLocation(irp);
ASSERT(irpStack->MajorFunction == IRP_MJ_PNP);
irpStack->MinorFunction = IRP_MN_QUERY_INTERFACE;
irpStack->Parameters.QueryInterface.InterfaceType = (PGUID) &GUID_PCI_BUS_INTERFACE_STANDARD;
irpStack->Parameters.QueryInterface.Version = PCI_BUS_INTERFACE_STANDARD_VERSION;
irpStack->Parameters.QueryInterface.Size = sizeof (PCI_BUS_INTERFACE_STANDARD);
irpStack->Parameters.QueryInterface.Interface = (PINTERFACE) interface;
irpStack->Parameters.QueryInterface.InterfaceSpecificData = NULL;
//
// Call the driver and wait for completion
//
status = IoCallDriver(targetDevice, irp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&irpCompleted, Executive, KernelMode, FALSE, NULL);
status = statusBlock.Status;
}
if (NT_SUCCESS(status)) {
FdoExtension->PciBusInterface = interface;
//
// The interface is already referenced when we get it so we don't need
// to reference it again.
//
} else {
//
// We don't have an interface
//
FdoExtension->PciBusInterface = NULL;
ExFreePool(interface);
}
//
// Ok we're done with this stack
//
ObDereferenceObject(targetDevice);
return status;
cleanup:
if (targetDevice) {
ObDereferenceObject(targetDevice);
}
if (interface) {
ExFreePool(interface);
}
return status;
}
NTSTATUS
PciGetConfigHandlers(
IN PPCI_FDO_EXTENSION FdoExtension
)
/*++
Routine Description:
This routine attempts to get pnp style config handlers from the PCI busses
enumerator and if they are not provided falls back on using the HAL bus
handler method.
Arguments:
FdoExtension - this PCI bus's FDO extension
Return Value:
STATUS_SUCCESS, if the PDO provided handlers
Notes:
--*/
{
NTSTATUS status;
PPCIBUSDATA BusData;
ASSERT(FdoExtension->BusHandler == NULL);
//
// Check if this is a root bus
//
if (PCI_IS_ROOT_FDO(FdoExtension)) {
ASSERT(FdoExtension->PciBusInterface == NULL);
//
// Check to see if our parent is offering
// functions for reading and writing config space.
//
status = PciQueryForPciBusInterface(FdoExtension);
if (NT_SUCCESS(status)) {
//
// If we have an interface we support numbering of busses
//
PciAssignBusNumbers = TRUE;
} else {
//
// We better not think we can number busses - we should only ever
// get here if one root provides an interface and the other does not
//
ASSERT(!PciAssignBusNumbers);
}
} else {
//
// Check if our root has a PciBusInterface - which it got from above
//
if (FdoExtension->BusRootFdoExtension->PciBusInterface) {
return STATUS_SUCCESS;
} else {
//
// Set status so we get a bus handler for this bus
//
status = STATUS_NOT_SUPPORTED;
}
}
if (!NT_SUCCESS(status)) {
ASSERT(status == STATUS_NOT_SUPPORTED);
//
// Make sure we arn't trying to get a bus handler for a hot plug
// capable machine
//
ASSERT(!PciAssignBusNumbers);
//
// We couldn't find config handlers the PnP way,
// build them from the HAL bus handlers.
//
FdoExtension->BusHandler =
HalReferenceHandlerForBus(PCIBus, FdoExtension->BaseBus);
if (!FdoExtension->BusHandler) {
//
// This must be a bus that arrived hot. We only support hot anything
// on ACPI machines and they should have provided a PCI_BUS interface
// at the root. Fail the add for this new bus.
//
return STATUS_INVALID_DEVICE_REQUEST; // better code?
}
}
return STATUS_SUCCESS;
}
NTSTATUS
PciExternalReadDeviceConfig(
IN PPCI_PDO_EXTENSION Pdo,
IN PVOID Buffer,
IN ULONG Offset,
IN ULONG Length
)
/*++
Routine Description:
Called when agents outside the PCI driver want to access config space
(either from a READ_CONFIG IRP or through BUS_INTERFACE_STANDARD).
This function performs extra sanity checks and sanitization on the
arguments and also double buffers the data as Buffer might be
pageable and we access config space at high IRQL.
Arguments:
Pdo - The PDO representing the device who's config space we want
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Return Value:
None
--*/
{
PUCHAR interruptLine;
UCHAR doubleBuffer[sizeof(PCI_COMMON_CONFIG)];
//
// Validate the request
//
if ((Length + Offset) > sizeof(PCI_COMMON_CONFIG)) {
return STATUS_INVALID_DEVICE_REQUEST;
}
//
// Read the data into a buffer allocated on the stack with
// is guaranteed to not be paged as we access config space
// at > DISPATCH_LEVEL and the DDK says that the buffer
// *should* be in paged pool.
//
PciReadDeviceConfig(Pdo, &doubleBuffer[Offset], Offset, Length);
//
// If we are reading the interrupt line register then adjust it.
//
if ((Pdo->InterruptPin != 0) &&
(Offset <= INT_LINE_OFFSET) &&
(Offset + Length > INT_LINE_OFFSET)) {
doubleBuffer[INT_LINE_OFFSET] = Pdo->AdjustedInterruptLine;
}
RtlCopyMemory(Buffer, &doubleBuffer[Offset], Length);
return STATUS_SUCCESS;
}
NTSTATUS
PciExternalWriteDeviceConfig(
IN PPCI_PDO_EXTENSION Pdo,
IN PVOID Buffer,
IN ULONG Offset,
IN ULONG Length
)
/*++
Routine Description:
Called when agents outside the PCI driver want to access config space
(either from a WRITE_CONFIG IRP or through BUS_INTERFACE_STANDARD).
This function performs extra sanity checks and sanitization on the
arguments and also double buffers the data as Buffer might be
pageable and we access config space at high IRQL.
Arguments:
Pdo - The PDO representing the device who's config space we want
Buffer - A buffer where the data will be read or written
Offset - The byte offset in config space where we should start to read/write
Length - The number of bytes to read/write
Return Value:
None
--*/
{
PUCHAR interruptLine;
UCHAR doubleBuffer[255];
BOOLEAN illegalAccess = FALSE;
PVERIFIER_DATA verifierData;
//
// Validate the request
//
if ((Length + Offset) > sizeof(PCI_COMMON_CONFIG)) {
return STATUS_INVALID_DEVICE_REQUEST;
}
//
// Make sure they are not touching registers they should not be. For
// backward compatiblity we will just complain and let the request through.
//
switch (Pdo->HeaderType) {
case PCI_DEVICE_TYPE:
//
// They should not be writing to their BARS including the ROM BAR
//
if (INTERSECT_CONFIG_FIELD(Offset, Length, u.type0.BaseAddresses)
|| INTERSECT_CONFIG_FIELD(Offset, Length, u.type0.ROMBaseAddress)) {
illegalAccess = TRUE;
}
break;
case PCI_BRIDGE_TYPE:
//
// For bridges they should not touch the bars, the base and limit registers,
// the bus numbers or bridge control
//
if (INTERSECT_CONFIG_FIELD_RANGE(Offset, Length, u.type1.BaseAddresses, u.type1.SubordinateBus)
|| INTERSECT_CONFIG_FIELD_RANGE(Offset, Length, u.type1.IOBase, u.type1.IOLimit)
|| INTERSECT_CONFIG_FIELD_RANGE(Offset, Length, u.type1.MemoryBase, u.type1.IOLimitUpper16)
|| INTERSECT_CONFIG_FIELD(Offset, Length, u.type1.ROMBaseAddress)) {
illegalAccess = TRUE;
}
break;
case PCI_CARDBUS_BRIDGE_TYPE:
//
// For bridges they should not touch the bars, the base and limit registers
// or the bus numbers. Bridge control is modified by PCICIA to control cardbus
// IRQ routing so must be ok.
//
if (INTERSECT_CONFIG_FIELD(Offset, Length, u.type2.SocketRegistersBaseAddress)
|| INTERSECT_CONFIG_FIELD_RANGE(Offset, Length, u.type2.PrimaryBus, u.type2.SubordinateBus)
|| INTERSECT_CONFIG_FIELD(Offset, Length, u.type2.Range)) {
illegalAccess = TRUE;
}
break;
}
if (illegalAccess) {
verifierData = PciVerifierRetrieveFailureData(
PCI_VERIFIER_PROTECTED_CONFIGSPACE_ACCESS
);
ASSERT(verifierData);
//
// We fail the devnode instead of the driver because we don't actually
// have an address to pass to the driver verifier.
//
VfFailDeviceNode(
Pdo->PhysicalDeviceObject,
PCI_VERIFIER_DETECTED_VIOLATION,
PCI_VERIFIER_PROTECTED_CONFIGSPACE_ACCESS,
verifierData->FailureClass,
&verifierData->Flags,
verifierData->FailureText,
"%DevObj%Ulong%Ulong",
Pdo->PhysicalDeviceObject,
Offset,
Length
);
}
//
// Copy the data into a buffer allocated on the stack with
// is guaranteed to not be paged as we access config space
// at > DISPATCH_LEVEL and the DDK says that the buffer
// *should* be in paged pool.
//
RtlCopyMemory(doubleBuffer, Buffer, Length);
//
// If we are writing the interrupt line register then adjust it so we write
// the raw value back again
//
if ((Pdo->InterruptPin != 0) &&
(Offset <= INT_LINE_OFFSET) &&
(Offset + Length > INT_LINE_OFFSET)) {
interruptLine = (PUCHAR)doubleBuffer + INT_LINE_OFFSET - Offset;
//
// Adjust the interrupt line with what the HAL wants us to see
//
*interruptLine = Pdo->RawInterruptLine;
}
PciWriteDeviceConfig(Pdo, doubleBuffer, Offset, Length);
return STATUS_SUCCESS;
}