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.
633 lines
18 KiB
633 lines
18 KiB
/*++
|
|
|
|
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, HalpSetAcpiIrqHack)
|
|
#pragma alloc_text(PAGELK, HalpClearSlpSmiStsInICH)
|
|
#endif
|
|
|
|
|
|
NTSTATUS
|
|
HalpGetChipHacks(
|
|
IN USHORT VendorId,
|
|
IN USHORT DeviceId,
|
|
IN UCHAR RevisionId 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
|
|
RevisionID - PCI Revision ID of chip, if applicable
|
|
HackFlags - value read from registry
|
|
|
|
--*/
|
|
{
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING UnicodeString;
|
|
NTSTATUS Status;
|
|
HANDLE Handle = NULL;
|
|
ULONG Length;
|
|
WCHAR buffer[9];
|
|
|
|
struct {
|
|
KEY_VALUE_PARTIAL_INFORMATION Inf;
|
|
UCHAR Data[3];
|
|
} PartialInformation;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Open current control set
|
|
//
|
|
|
|
RtlInitUnicodeString (&UnicodeString,
|
|
L"\\REGISTRY\\MACHINE\\SYSTEM\\CURRENTCONTROLSET\\Control\\HAL");
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&UnicodeString,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
(PSECURITY_DESCRIPTOR) NULL);
|
|
|
|
Status = ZwOpenKey(&Handle, KEY_READ, &ObjectAttributes);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
swprintf(buffer, L"%04X%04X", VendorId, DeviceId);
|
|
|
|
RtlInitUnicodeString(&UnicodeString, buffer);
|
|
|
|
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));
|
|
|
|
//
|
|
// If revisionId is specified, test if there are updated flags
|
|
// for this rev
|
|
//
|
|
*HackFlags =
|
|
((RevisionId != 0) &&
|
|
(RevisionId >= HACK_REVISION(*HackFlags))) ?
|
|
REVISED_HACKS(*HackFlags):
|
|
BASE_HACKS(*HackFlags);
|
|
}
|
|
|
|
ZwClose(Handle);
|
|
|
|
return Status;
|
|
}
|
|
|
|
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. Since PCI addresses are naturally aligned powers
|
|
// of two we don't need to worry about this spanning two pages.
|
|
//
|
|
|
|
ohci = HalpMapPhysicalMemory64(BarAddr, 1);
|
|
|
|
//
|
|
// 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, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_ADDRESS_MASK;
|
|
|
|
//
|
|
// At PM_BASE + 0x30 in I/O space, we have the SMI_EN
|
|
// register.
|
|
//
|
|
|
|
SmiEn = READ_PORT_ULONG((PULONG)UlongToPtr(PmBase + 0x30));
|
|
|
|
//
|
|
// Clear bit 3, LEGACY_USB_EN.
|
|
//
|
|
|
|
SmiEn &= ~8;
|
|
WRITE_PORT_ULONG((PULONG)UlongToPtr(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_ADDRESS_MASK;
|
|
|
|
SmiEn = READ_PORT_UCHAR(UlongToPtr(PmBase + 0x30));
|
|
|
|
if (!(SmiEn & 0x10)) {
|
|
|
|
//
|
|
// The SLP_SMI_EN bit in the SMI_EN register was
|
|
// clear.
|
|
//
|
|
|
|
SmiSts = READ_PORT_UCHAR(UlongToPtr(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(UlongToPtr(PmBase + 0x34), 0x10);
|
|
}
|
|
}
|
|
}
|
|
}
|