/* * Copyright (c) 2006-2008 Advanced Micro Devices,Inc. ("AMD"). * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ //****************************************************************************** //* Implements the virtual register handler //****************************************************************************** #include "VSA2.H" #include "PROTOS.H" #include "SYSMGR.H" #include "VR.H" #include "PCI.H" #include "CHIPSET.H" // External variables extern ULONG Saved_EAX; extern USHORT Saved_AX; extern SmiHeader SMM_Header; extern ULONG IRQ_Mask; extern ULONG Audio_IRQ; extern ULONG NativeAudioStatus; extern ULONG MsgPacket[]; extern ULONG VSM_Ptrs[]; extern Hardware HardwareInfo; // External function prototypes extern void pascal Send_Synchronous_Event(EVENT, SmiHeader *); extern void pascal VR_Miscellaneous_Write(UCHAR); extern void pascal Return_Virtual_Value(SmiHeader *, ULONG); extern ULONG pascal VR_Miscellaneous_Read(UCHAR); //*********************************************************************** // Initialize virtual register trapping //*********************************************************************** void Init_Virtual_Regs(void) { USHORT PCI_Addr; ULONG Enable; // Allocate a 4-byte I/O BAR0 associated with Northbridge header PCI_Addr = Allocate_BAR(RESOURCE_SCIO, BAR0, 4, 0x0000, HardwareInfo.CPU_ID); // Set the base address Virtual_PCI_Write_Handler(PCI_Addr, DWORD_IO, VRC_INDEX); // Enable the I/O space PCI_Addr = (PCI_Addr & 0xFF00) + COMMAND; Enable = Virtual_PCI_Read_Handler(PCI_Addr); Enable |= IO_SPACE; Virtual_PCI_Write_Handler(PCI_Addr, DWORD_IO, Enable); } //*********************************************************************** // Handles audio virtual registers when there is no audio VSM. //*********************************************************************** void Handle_Audio_VRC(UCHAR Index, ULONG Trapped_Data, USHORT Trapped_Flags, UCHAR Size) { static USHORT AudioVRs[MAX_AUDIO+1] = { 0x201,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xAC97,0xFFFF,0xFFFF,0xFFFF,0x04F0 }; // If illegal register, ignore it if (Index > MAX_AUDIO) { return; } if (Trapped_Flags & SMI_FLAGS_OUTPUT) { switch (Index) { // Read-only registers case CODEC_TYPE: case AUDIO_VERSION: return; // Native audio IRQ case AUDIO_IRQ: // Set previous audio IRQ to external IRQ_Mask |= Audio_IRQ << 16; Audio_IRQ = 0; if ((UCHAR)Trapped_Data) { Audio_IRQ = 0x00000001L << (UCHAR)Trapped_Data; } break; // Native audio status pointer case STATUS_PTR: switch (SMM_Header.data_size) { case WORD_IO: (USHORT)NativeAudioStatus = (USHORT)Trapped_Data; break; case DWORD_IO: NativeAudioStatus = Trapped_Data; break; } break; } AudioVRs[Index] = (USHORT)Trapped_Data; } else { // Return virtual register value Saved_AX = AudioVRs[Index]; // STATUS_PTR may be read as a DWORD if (Index == STATUS_PTR && DWORD_IO == Size) { Saved_EAX = NativeAudioStatus; } } } typedef struct { union { USHORT ClassIndex; struct { UCHAR Index; UCHAR Class; }; }; } CLASS_INDEX; typedef struct { union { ULONG Dword; struct { USHORT Word0; USHORT Word1; }; struct { UCHAR Byte0; UCHAR Byte1; UCHAR Byte2; UCHAR Byte3; }; }; } DWORD; #define VR_LOCKED 0 #define VR_UNLOCKED 1 #define VR_INDEXED 2 //*********************************************************************** // Handles accesses to Virtual Registers //*********************************************************************** void VR_Handler(SmiHeader * SmiHdr) { USHORT Trapped_Addr; ULONG Data, SegOffset; UCHAR Trapped_Flags, IO_Write, Addr_2LSBs, Size; DWORD Trapped_Data; static UCHAR VR_LockState = 0; static CLASS_INDEX VirtReg; // Get info from SMM header Trapped_Addr = (USHORT)SmiHdr->IO_addr; Trapped_Flags = (UCHAR)SmiHdr->SMI_Flags_Ushort; IO_Write = (UCHAR)Trapped_Flags & SMI_FLAGS_OUTPUT ? 1 : 0; Size = (UCHAR)SmiHdr->data_size; Addr_2LSBs = (UCHAR)Trapped_Addr & 3; Trapped_Data.Dword = SmiHdr->write_data; SegOffset = (ULONG) SmiHdr->_CS.selector << 16; SegOffset += (USHORT)SmiHdr->Current_EIP; // Validate the I/O as a valid virtual register access switch (Size) { case DWORD_IO: // Allow unlock & index to occur in a single SMI if (VR_LockState == VR_LOCKED) { if (IO_Write && (VR_UNLOCK == Trapped_Data.Word1)) { VirtReg.ClassIndex = Trapped_Data.Word0; VR_LockState = VR_INDEXED; return; } } case BYTE_IO: // Byte I/O is illegal for virtual registers. Start over. VR_LockState = VR_LOCKED; return; } // end switch(Size) switch(Addr_2LSBs) { // Virtual register INDEX case 0: switch (VR_LockState) { // Virtual registers have been unlocked & Class:Index has been written, // but VR class::index is being re-written. case VR_INDEXED: if (!IO_Write) { goto ReturnClassIndex; } VR_LockState = VR_LOCKED; // Virtual registers have not been unlocked yet case VR_LOCKED: if (IO_Write) { if (VR_UNLOCK == Trapped_Data.Word0) { // Virtual registers have been unlocked. // Advance to the next VR_LockState. VR_LockState = VR_UNLOCKED; } } return; // Virtual registers have been unlocked. // Next VR write should be Class::Index case VR_UNLOCKED: if (IO_Write) { // Record the VR index & advance to the next VR_LockState VirtReg.ClassIndex = Trapped_Data.Word0; // Report error if illegal VR class if (VirtReg.Class > MAX_VR_CLASS && VirtReg.Class < 0x80) { Log_Error("Invalid virtual register class 0x%02X", VirtReg.Class); goto VR_Error; } VR_LockState = VR_INDEXED; } else { ReturnClassIndex: // Reading back Class::Index Return_Virtual_Value(SmiHdr, (ULONG)VirtReg.ClassIndex); } return; } // end switch (VR_LockState) Log_Error("Incorrect virtual register sequence"); goto VR_Error; // Virtual register DATA case 2: if (VR_INDEXED == VR_LockState) { switch (VirtReg.Class) { case VRC_MISCELLANEOUS: if (IO_Write) { VR_Miscellaneous_Write(VirtReg.Index); } else { Data = VR_Miscellaneous_Read(VirtReg.Index); switch (VirtReg.Index) { case SIGNATURE: case HIGH_MEM_ACCESS: (UCHAR)SmiHdr->data_size = DWORD_IO; case VSA_VERSION_NUM: case VSM_VERSION: // Return virtualized PCI device value to the right environment Return_Virtual_Value(SmiHdr, Data); break; } } // If access was to an invalid MSR, ignore the SSMI_FLAG if (VirtReg.Index == MSR_ACCESS) { SmiHdr->SMI_Flags.IO_Trap = 0; SmiHdr->SMI_Flags.Ext_IO_Trap = 0; } break; default: // Prepare message packet MsgPacket[1] = (ULONG)VirtReg.ClassIndex; MsgPacket[2] = 0; // Assume I/O read MsgPacket[3] = 0; if (IO_Write) { (UCHAR) MsgPacket[2] = 1; // I/O write (USHORT)MsgPacket[3] = Trapped_Data.Word0; } // Send EVENT_VIRTUAL_REGISTER to registered VSM(s) Send_Synchronous_Event(EVENT_VIRTUAL_REGISTER, SmiHdr); break; } // switch (Class) } else { static UCHAR * RdWr[] = {"Read", "Write"}; Log_Error("%s of locked VR %02X:%02X at %04X:%08X ", RdWr[IO_Write], VirtReg.Class, VirtReg.Index, SmiHdr->_CS.selector, SmiHdr->Current_EIP); VR_Error: // Ignore writes. Return 0's for reads. if (!IO_Write) { // NOTE: We know it is a WORD_IO from above validation. Saved_AX = 0; } } break; default: // Report mis-aligned access to virtual register Log_Error("Mis-aligned access to virtual register 0x04X", Trapped_Addr); break; } // end switch(Addr_2LSBs) // Reset the VR state machine VR_LockState = VR_LOCKED; }