721 lines
19 KiB
C
721 lines
19 KiB
C
/*++
|
||
|
||
Copyright (c) 2000 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
chiphacks.c
|
||
|
||
Abstract:
|
||
|
||
Implements utilities for finding and hacking
|
||
various chipsets
|
||
|
||
Author:
|
||
|
||
Jake Oshins (jakeo) 10/02/2000
|
||
|
||
Environment:
|
||
|
||
Kernel mode only.
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "chiphacks.h"
|
||
#include "stdio.h"
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE, HalpGetChipHacks)
|
||
#pragma alloc_text(PAGE, HalpDoesChipNeedHack)
|
||
#pragma alloc_text(PAGE, HalpSetAcpiIrqHack)
|
||
#pragma alloc_text(PAGELK, HalpClearSlpSmiStsInICH)
|
||
#endif
|
||
|
||
|
||
NTSTATUS
|
||
HalpGetChipHacks(
|
||
IN USHORT VendorId,
|
||
IN USHORT DeviceId,
|
||
IN ULONG Ssid OPTIONAL,
|
||
OUT ULONG *HackFlags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine looks under HKLM\System\CurrentControlSet\Control\HAL
|
||
to see if there is an entry for the PCI device being
|
||
described. If so, it returns a set of associated flags.
|
||
|
||
Arguments:
|
||
|
||
VendorId - PCI Vendor ID of chip
|
||
DeviceId - PCI Device ID of chip
|
||
Ssid - PCI subsystem ID of chip, if applicable
|
||
HackFlags - value read from registry
|
||
|
||
--*/
|
||
{
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
UNICODE_STRING UnicodeString;
|
||
STRING AString;
|
||
NTSTATUS Status;
|
||
HANDLE BaseHandle = NULL;
|
||
HANDLE Handle = NULL;
|
||
ULONG disposition;
|
||
ULONG Length;
|
||
CHAR buffer[20] = {0};
|
||
|
||
struct {
|
||
KEY_VALUE_PARTIAL_INFORMATION Inf;
|
||
UCHAR Data[3];
|
||
} PartialInformation;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Open current control set
|
||
//
|
||
|
||
RtlInitUnicodeString (&UnicodeString,
|
||
L"\\REGISTRY\\MACHINE\\SYSTEM\\CURRENTCONTROLSET\\Control");
|
||
|
||
InitializeObjectAttributes(&ObjectAttributes,
|
||
&UnicodeString,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
(PSECURITY_DESCRIPTOR) NULL);
|
||
|
||
Status = ZwOpenKey (&BaseHandle,
|
||
KEY_READ,
|
||
&ObjectAttributes);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
return STATUS_UNSUCCESSFUL;
|
||
}
|
||
|
||
// Get the right key
|
||
|
||
RtlInitUnicodeString (&UnicodeString,
|
||
L"HAL");
|
||
|
||
InitializeObjectAttributes(&ObjectAttributes,
|
||
&UnicodeString,
|
||
OBJ_CASE_INSENSITIVE,
|
||
BaseHandle,
|
||
(PSECURITY_DESCRIPTOR) NULL);
|
||
|
||
Status = ZwCreateKey (&Handle,
|
||
KEY_READ,
|
||
&ObjectAttributes,
|
||
0,
|
||
(PUNICODE_STRING) NULL,
|
||
REG_OPTION_NON_VOLATILE,
|
||
&disposition);
|
||
|
||
if(!NT_SUCCESS(Status)) {
|
||
goto GetChipHacksCleanup;
|
||
}
|
||
|
||
//
|
||
// Look in the registry to see if the registry
|
||
// contains an entry for this chip. The first
|
||
// step is to build a string that defines the chip.
|
||
//
|
||
|
||
if (Ssid) {
|
||
|
||
sprintf(buffer, "%04x%04x%08x",
|
||
VendorId,
|
||
DeviceId,
|
||
Ssid);
|
||
|
||
} else {
|
||
|
||
sprintf(buffer, "%04x%04x",
|
||
VendorId,
|
||
DeviceId);
|
||
|
||
}
|
||
|
||
RtlInitAnsiString(&AString, buffer);
|
||
|
||
RtlUpperString(&AString, &AString);
|
||
|
||
Status = STATUS_NOT_FOUND;
|
||
|
||
if (NT_SUCCESS(RtlAnsiStringToUnicodeString(&UnicodeString,
|
||
&AString,
|
||
TRUE))) {
|
||
|
||
Status = ZwQueryValueKey (Handle,
|
||
&UnicodeString,
|
||
KeyValuePartialInformation,
|
||
&PartialInformation,
|
||
sizeof (PartialInformation),
|
||
&Length);
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
|
||
//
|
||
// We found a value in the registry
|
||
// that corresponds with the chip
|
||
// we just ran across.
|
||
//
|
||
|
||
*HackFlags = *((PULONG)(PartialInformation.Inf.Data));
|
||
}
|
||
|
||
RtlFreeUnicodeString(&UnicodeString);
|
||
}
|
||
|
||
GetChipHacksCleanup:
|
||
|
||
if (Handle) ZwClose (Handle);
|
||
if (BaseHandle) ZwClose (BaseHandle);
|
||
|
||
return Status;
|
||
}
|
||
|
||
BOOLEAN
|
||
HalpDoesChipNeedHack(
|
||
IN USHORT VendorId,
|
||
IN USHORT DeviceId,
|
||
IN ULONG Ssid OPTIONAL,
|
||
IN ULONG HackFlags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is a wrapper for HalpGetChipHacks.
|
||
|
||
Arguments:
|
||
|
||
VendorId - PCI Vendor ID of chip
|
||
DeviceId - PCI Device ID of chip
|
||
Ssid - PCI subsystem ID of chip, if applicable
|
||
HackFlags - value to compare with registry
|
||
|
||
--*/
|
||
{
|
||
ULONG flagsFromRegistry;
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE();
|
||
|
||
status = HalpGetChipHacks(VendorId,
|
||
DeviceId,
|
||
Ssid,
|
||
&flagsFromRegistry);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
|
||
if (HackFlags & flagsFromRegistry) {
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
VOID
|
||
HalpStopOhciInterrupt(
|
||
ULONG BusNumber,
|
||
PCI_SLOT_NUMBER SlotNumber
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine shuts off the interrupt from an OHCI
|
||
USB controller. This may be necessary because
|
||
a BIOS may enable the PCI interrupt from a USB controller
|
||
in order to do "legacy USB support" where it translates
|
||
USB keyboard and mouse traffic into something that DOS
|
||
can use. (Our loader and all of Win9x approximate DOS.)
|
||
|
||
Arguments:
|
||
|
||
BusNumber - Bus number of OHCI controller
|
||
SlotNumber - Slot number of OHCI controller
|
||
|
||
Note:
|
||
|
||
This routine also may need to be called at raised IRQL
|
||
when returning from hibernation.
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// 7.1.2 HcControl Register
|
||
//
|
||
#define HcCtrl_InterruptRouting 0x00000100L
|
||
|
||
//
|
||
// 7.1.3 HcCommandStatus Register
|
||
//
|
||
#define HcCmd_OwnershipChangeRequest 0x00000008L
|
||
|
||
//
|
||
// 7.1.4 HcInterrruptStatus Register
|
||
// 7.1.5 HcInterruptEnable Register
|
||
// 7.1.6 HcInterruptDisable Register
|
||
//
|
||
#define HcInt_SchedulingOverrun 0x00000001L
|
||
#define HcInt_WritebackDoneHead 0x00000002L
|
||
#define HcInt_StartOfFrame 0x00000004L
|
||
#define HcInt_ResumeDetected 0x00000008L
|
||
#define HcInt_UnrecoverableError 0x00000010L
|
||
#define HcInt_FrameNumberOverflow 0x00000020L
|
||
#define HcInt_RootHubStatusChange 0x00000040L
|
||
#define HcInt_OwnershipChange 0x40000000L
|
||
#define HcInt_MasterInterruptEnable 0x80000000L
|
||
|
||
//
|
||
// Host Controler Hardware Registers as accessed in memory
|
||
//
|
||
struct {
|
||
// 0 0x00 - 0,4,8,c
|
||
ULONG HcRevision;
|
||
ULONG HcControl;
|
||
ULONG HcCommandStatus;
|
||
ULONG HcInterruptStatus; // use HcInt flags below
|
||
// 1 0x10
|
||
ULONG HcInterruptEnable; // use HcInt flags below
|
||
ULONG HcInterruptDisable; // use HcInt flags below
|
||
} volatile *ohci;
|
||
|
||
PCI_COMMON_CONFIG PciHeader;
|
||
PHYSICAL_ADDRESS BarAddr;
|
||
|
||
HalGetBusData (
|
||
PCIConfiguration,
|
||
BusNumber,
|
||
SlotNumber.u.AsULONG,
|
||
&PciHeader,
|
||
PCI_COMMON_HDR_LENGTH
|
||
);
|
||
|
||
if (PciHeader.Command & PCI_ENABLE_MEMORY_SPACE) {
|
||
|
||
//
|
||
// The controller is enabled.
|
||
//
|
||
|
||
BarAddr.HighPart = 0;
|
||
BarAddr.LowPart = (PciHeader.u.type0.BaseAddresses[0] & PCI_ADDRESS_MEMORY_ADDRESS_MASK);
|
||
|
||
if (BarAddr.LowPart != 0) {
|
||
|
||
//
|
||
// The BAR is populated. So map an address for it.
|
||
//
|
||
|
||
ohci = HalpMapPhysicalMemory64(BarAddr, 2);
|
||
|
||
//
|
||
// Set the interrupt disable bit, but disable SMM control of the
|
||
// host controller first.
|
||
//
|
||
|
||
if (ohci) {
|
||
|
||
if (ohci->HcControl & HcCtrl_InterruptRouting) {
|
||
|
||
if ((ohci->HcControl == HcCtrl_InterruptRouting) &&
|
||
(ohci->HcInterruptEnable == 0)) {
|
||
|
||
// Major assumption: If HcCtrl_InterruptRouting is
|
||
// set but no other bits in HcControl are set, i.e.
|
||
// HCFS==UsbReset, and no interrupts are enabled, then
|
||
// assume that the BIOS is not actually using the host
|
||
// controller. In this case just clear the erroneously
|
||
// set HcCtrl_InterruptRouting.
|
||
//
|
||
ohci->HcControl = 0; // Clear HcCtrl_InterruptRouting
|
||
|
||
} else {
|
||
|
||
ULONG msCount;
|
||
|
||
//
|
||
// A SMM driver does own the HC, it will take some time
|
||
// to get the SMM driver to relinquish control of the
|
||
// HC. We will ping the SMM driver, and then wait
|
||
// repeatedly until the SMM driver has relinquished
|
||
// control of the HC.
|
||
//
|
||
|
||
// Disable the root hub status change to prevent an
|
||
// unhandled interrupt from being asserted after
|
||
// handoff. (Not clear what platforms really require
|
||
// this...)
|
||
//
|
||
ohci->HcInterruptDisable = HcInt_RootHubStatusChange;
|
||
|
||
// The HcInt_MasterInterruptEnable and HcInt_OwnershipChange
|
||
// bits should already be set, but make sure they are.
|
||
//
|
||
ohci->HcInterruptEnable = HcInt_MasterInterruptEnable |
|
||
HcInt_OwnershipChange;
|
||
|
||
// Ping the SMM driver to relinquish control of the HC.
|
||
//
|
||
ohci->HcCommandStatus = HcCmd_OwnershipChangeRequest;
|
||
|
||
// Wait 500ms for the SMM driver to relinquish control.
|
||
//
|
||
for (msCount = 0; msCount < 500; msCount++) {
|
||
|
||
KeStallExecutionProcessor(1000);
|
||
|
||
if (!(ohci->HcControl & HcCtrl_InterruptRouting)) {
|
||
// SMM driver has relinquished control.
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ohci->HcInterruptDisable = HcInt_MasterInterruptEnable;
|
||
|
||
//
|
||
// Unmap the virtual address.
|
||
//
|
||
|
||
HalpUnmapVirtualAddress((PVOID)ohci, 2);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
VOID
|
||
HalpStopUhciInterrupt(
|
||
ULONG BusNumber,
|
||
PCI_SLOT_NUMBER SlotNumber,
|
||
BOOLEAN ResetHostController
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine shuts off the interrupt from an UHCI
|
||
USB controller. This may be necessary because
|
||
a BIOS may enable the PCI interrupt from a USB controller
|
||
in order to do "legacy USB support" where it translates
|
||
USB keyboard and mouse traffic into something that DOS
|
||
can use. (Our loader and all of Win9x approximate DOS.)
|
||
|
||
Arguments:
|
||
|
||
BusNumber - Bus number of UHCI controller
|
||
SlotNumber - Slot number of UHCI controller
|
||
|
||
Note:
|
||
|
||
This routine also may need to be called at raised IRQL
|
||
when returning from hibernation.
|
||
|
||
--*/
|
||
{
|
||
ULONG Usb = 0;
|
||
USHORT cmd;
|
||
PCI_COMMON_CONFIG PciHeader;
|
||
|
||
if (ResetHostController) {
|
||
|
||
//
|
||
// Clear out the host controller legacy support register
|
||
// prior to handing the USB to the USB driver, because we
|
||
// don't want any SMIs being generated.
|
||
//
|
||
|
||
Usb = 0x0000;
|
||
|
||
HalSetBusDataByOffset (
|
||
PCIConfiguration,
|
||
BusNumber,
|
||
SlotNumber.u.AsULONG,
|
||
&Usb,
|
||
0xc0,
|
||
sizeof(ULONG)
|
||
);
|
||
|
||
//
|
||
// Put the USB controller into reset, as it may share it's
|
||
// PIRQD line with another USB controller on the chipset.
|
||
// This is not a problem unless the bios is running in legacy
|
||
// mode and causing interrupts. In this case, the minute PIRQD
|
||
// gets flipped by one usbuhci controller, the other could
|
||
// start generating unhandled interrupts and hang the system.
|
||
// This is the case with the ICH2 chipset.
|
||
//
|
||
|
||
HalGetBusData (
|
||
PCIConfiguration,
|
||
BusNumber,
|
||
SlotNumber.u.AsULONG,
|
||
&PciHeader,
|
||
PCI_COMMON_HDR_LENGTH
|
||
);
|
||
|
||
if (PciHeader.Command & PCI_ENABLE_IO_SPACE) {
|
||
|
||
//
|
||
// The controller is enabled.
|
||
//
|
||
|
||
Usb = (PciHeader.u.type0.BaseAddresses[4] & PCI_ADDRESS_IO_ADDRESS_MASK);
|
||
|
||
if (Usb != 0 && Usb < 0x0000ffff) {
|
||
|
||
// Valid I/O address.
|
||
|
||
//
|
||
// If we are returning from suspend, don't put the controller
|
||
// into reset.
|
||
//
|
||
cmd = READ_PORT_USHORT(UlongToPtr(Usb));
|
||
|
||
if (!(cmd & 0x0008)) {
|
||
//
|
||
// Put the controller in reset. Usbuhci will take it out of reset
|
||
// when it grabs it.
|
||
//
|
||
|
||
cmd = 0x0004;
|
||
|
||
WRITE_PORT_USHORT(UlongToPtr(Usb), cmd);
|
||
|
||
//
|
||
// Wait 10ms and then take the controller out of reset.
|
||
//
|
||
|
||
KeStallExecutionProcessor(10000);
|
||
|
||
cmd &= 0x0000;
|
||
|
||
WRITE_PORT_USHORT(UlongToPtr(Usb), cmd);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// Shut off the interrupt for the USB controller, as it
|
||
// is very frequently the reason that the machine freezes
|
||
// during boot. Anding the register with ~0xbf00 clears bit
|
||
// 13, PIRQ Enable, which is the whole point. The rest of
|
||
// the bits just avoid writing registers that are "write
|
||
// one to clear."
|
||
//
|
||
|
||
HalGetBusDataByOffset (
|
||
PCIConfiguration,
|
||
BusNumber,
|
||
SlotNumber.u.AsULONG,
|
||
&Usb,
|
||
0xc0,
|
||
sizeof(ULONG)
|
||
);
|
||
|
||
Usb &= ~0xbf00;
|
||
|
||
HalSetBusDataByOffset (
|
||
PCIConfiguration,
|
||
BusNumber,
|
||
SlotNumber.u.AsULONG,
|
||
&Usb,
|
||
0xc0,
|
||
sizeof(ULONG)
|
||
);
|
||
|
||
}
|
||
}
|
||
|
||
VOID
|
||
HalpWhackICHUsbSmi(
|
||
ULONG BusNumber,
|
||
PCI_SLOT_NUMBER SlotNumber
|
||
)
|
||
{
|
||
ULONG PmBase = 0;
|
||
ULONG SmiEn;
|
||
|
||
//
|
||
// ICH (and the like) have the PM_BASE register in
|
||
// config space at offset 0x40.
|
||
//
|
||
|
||
HalGetBusDataByOffset (
|
||
PCIConfiguration,
|
||
BusNumber,
|
||
SlotNumber.u.AsULONG,
|
||
&PmBase,
|
||
0x40,
|
||
4);
|
||
|
||
if (!PmBase) {
|
||
return;
|
||
}
|
||
|
||
PmBase &= ~PCI_ADDRESS_IO_SPACE;
|
||
|
||
//
|
||
// At PM_BASE + 0x30 in I/O space, we have the SMI_EN
|
||
// register.
|
||
//
|
||
|
||
SmiEn = READ_PORT_ULONG((PULONG)(((PUCHAR)PmBase) + 0x30));
|
||
|
||
//
|
||
// Clear bit 3, LEGACY_USB_EN.
|
||
//
|
||
|
||
SmiEn &= ~8;
|
||
WRITE_PORT_ULONG((PULONG)(((PUCHAR)PmBase) + 0x30), SmiEn);
|
||
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
HalpSetAcpiIrqHack(
|
||
ULONG Value
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine sets the registry key that causes the
|
||
ACPI driver to attempt to put all PCI interrupts
|
||
on a single IRQ. While putting this hack here may
|
||
seem strange, the hack has to be applied before
|
||
an INFs are processed. And so much of the chip
|
||
recognizing code already exists here, duplicating
|
||
it in the ACPI driver would bloat the code and cause
|
||
us to do another PCI bus scan and registry search
|
||
during boot.
|
||
|
||
Arguments:
|
||
|
||
Value - This goes in the ACPI\Parameters\IRQDistribution
|
||
key.
|
||
|
||
--*/
|
||
{
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
UNICODE_STRING UnicodeString;
|
||
HANDLE BaseHandle = NULL;
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE();
|
||
|
||
RtlInitUnicodeString (&UnicodeString,
|
||
L"\\REGISTRY\\MACHINE\\SYSTEM\\CURRENTCONTROLSET\\Services\\ACPI\\Parameters");
|
||
|
||
InitializeObjectAttributes(&ObjectAttributes,
|
||
&UnicodeString,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
(PSECURITY_DESCRIPTOR) NULL);
|
||
|
||
status = ZwCreateKey (&BaseHandle,
|
||
KEY_WRITE,
|
||
&ObjectAttributes,
|
||
0,
|
||
(PUNICODE_STRING) NULL,
|
||
REG_OPTION_NON_VOLATILE,
|
||
NULL);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return;
|
||
}
|
||
|
||
RtlInitUnicodeString (&UnicodeString,
|
||
L"IRQDistribution");
|
||
|
||
status = ZwSetValueKey (BaseHandle,
|
||
&UnicodeString,
|
||
0,
|
||
REG_DWORD,
|
||
&Value,
|
||
sizeof(ULONG));
|
||
|
||
ASSERT(NT_SUCCESS(status));
|
||
ZwClose(BaseHandle);
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
HalpClearSlpSmiStsInICH(
|
||
VOID
|
||
)
|
||
{
|
||
PPCI_COMMON_CONFIG PciHeader;
|
||
UCHAR buffer[0x44] = {0};
|
||
ULONG PmBase;
|
||
UCHAR SmiSts, SmiEn;
|
||
|
||
PciHeader = (PPCI_COMMON_CONFIG)&buffer;
|
||
|
||
//
|
||
// ASUS has a BIOS bug that will leave the
|
||
// SLP_SMI_STS bit set even when the SLP_SMI_EN
|
||
// bit is clear. The BIOS will furthermore
|
||
// shut the machine down on the next SMI when
|
||
// this occurs.
|
||
//
|
||
|
||
|
||
//
|
||
// Check for ICH.
|
||
//
|
||
|
||
HalGetBusDataByOffset (
|
||
PCIConfiguration,
|
||
0,
|
||
0x1f,
|
||
PciHeader,
|
||
0,
|
||
0x44);
|
||
|
||
if ((PciHeader->VendorID == 0x8086) &&
|
||
(PciHeader->BaseClass == PCI_CLASS_BRIDGE_DEV) &&
|
||
(PciHeader->SubClass == PCI_SUBCLASS_BR_ISA)) {
|
||
|
||
//
|
||
// This is an ICH. Offset 0x40 will have an I/O BAR
|
||
// which is the PM_BASE register.
|
||
//
|
||
|
||
PmBase = *(PULONG)PciHeader->DeviceSpecific;
|
||
PmBase &= ~PCI_ADDRESS_IO_SPACE;
|
||
|
||
SmiEn = READ_PORT_UCHAR(((PUCHAR)PmBase) + 0x30);
|
||
|
||
if (!(SmiEn & 0x10)) {
|
||
|
||
//
|
||
// The SLP_SMI_EN bit in the SMI_EN register was
|
||
// clear.
|
||
//
|
||
|
||
SmiSts = READ_PORT_UCHAR(((PUCHAR)PmBase) + 0x34);
|
||
|
||
if (SmiSts & 0x10) {
|
||
|
||
//
|
||
// But the SLP_SMI_STS bit was set, implying
|
||
// that the ASUS BIOS is about to keel over.
|
||
// Clear the bit.
|
||
//
|
||
|
||
WRITE_PORT_UCHAR(((PUCHAR)PmBase) + 0x34, 0x10);
|
||
}
|
||
}
|
||
}
|
||
}
|