/*++ 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); } } } }