/******************************************************************************* * Copyright 2013-2016 Avago Technologies * Copyright (c) 2009 to 2012 PLX Technology Inc. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directorY of this source tree, or the * BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. ******************************************************************************/ /****************************************************************************** * * File Name: * * PciFunc.c * * Description: * * This file contains the PCI support functions * * Revision History: * * 12-01-16 : PLX SDK v7.25 * ******************************************************************************/ #include "PciFunc.h" /*********************************************** * Globals **********************************************/ // Global ECAM 64-bit address from ACPI table // // Probing is only enabled on i386 or x64 platforms since it requires // parsing ACPI tables. This is not supported on non-x86 platforms. // #if (defined(__i386__) || defined(__x86_64__) || defined(PLX_DOS)) && !defined(PLX_COSIM) static U32 Gbl_Acpi_Addr_ECAM[3] = { 0, 0, ACPI_PCIE_NOT_PROBED }; #else static U32 Gbl_Acpi_Addr_ECAM[3] = { 0, 0, ACPI_PCIE_ALWAYS_USE_OS }; #endif /******************************************************************************* * * Function : PlxPciRegisterRead_UseOS * * Description: Reads a PCI register using OS services * ******************************************************************************/ PLX_STATUS PlxPciRegisterRead_UseOS( PLX_DEVICE_KEY *pKey, U16 offset, U32 *pValue ) { // For PCIe register, use ECAM if bypassing OS if ((offset >= 0x100) && (Gbl_Acpi_Addr_ECAM[2] != ACPI_PCIE_DEFAULT_TO_OS) && (Gbl_Acpi_Addr_ECAM[2] != ACPI_PCIE_ALWAYS_USE_OS)) { return PlxPciExpressRegRead( pKey->domain, pKey->bus, pKey->slot, pKey->function, offset, pValue ); } // Use standard function by location return PlxPciRegRead_ByLoc( pKey->domain, pKey->bus, pKey->slot, pKey->function, offset, pValue, sizeof(U32) ); } /******************************************************************************* * * Function : PlxPciRegisterWrite_UseOS * * Description: Writes a value to a PCI register using OS services * ******************************************************************************/ PLX_STATUS PlxPciRegisterWrite_UseOS( PLX_DEVICE_KEY *pKey, U16 offset, U32 value ) { // For PCIe register, use ECAM if bypassing OS if ((offset >= 0x100) && (Gbl_Acpi_Addr_ECAM[2] != ACPI_PCIE_DEFAULT_TO_OS) && (Gbl_Acpi_Addr_ECAM[2] != ACPI_PCIE_ALWAYS_USE_OS)) { return PlxPciExpressRegWrite( pKey->domain, pKey->bus, pKey->slot, pKey->function, offset, value ); } // Use standard function by location return PlxPciRegWrite_ByLoc( pKey->domain, pKey->bus, pKey->slot, pKey->function, offset, value, sizeof(U32) ); } /******************************************************************************* * * Function : PlxPciRegRead_ByLoc * * Description: Reads a PCI register of a device specified by location * ******************************************************************************/ PLX_STATUS PlxPciRegRead_ByLoc( U8 domain, U8 bus, U8 slot, U8 function, U16 offset, VOID *pValue, U8 AccessSize ) { int rc; U8 BarNum; U32 Flags_OS; U32 RegValue; struct pci_dev *pPciDev; if (pValue == NULL) { return PLX_STATUS_NULL_PARAM; } // Verify access type & offset switch (AccessSize) { case sizeof(U8): *(U8*)pValue = (U8)-1; break; case sizeof(U16): *(U16*)pValue = (U16)-1; if (offset & 0x1) { return PLX_STATUS_INVALID_OFFSET; } break; case sizeof(U32): *(U32*)pValue = (U32)-1; if (offset & 0x3) { return PLX_STATUS_INVALID_OFFSET; } break; default: return PLX_STATUS_UNSUPPORTED; } // Locate PCI device pPciDev = Plx_pci_get_domain_bus_and_slot( domain, bus, PCI_DEVFN(slot, function) ); if (pPciDev == NULL) { DebugPrintf(( "ERROR - Device at [D%d %02X:%02X.%X] does not exist\n", domain, bus, slot, function )); return PLX_STATUS_INVALID_OBJECT; } // Assume non-VF device rc = -1; RegValue = 0; // If the device is a VF, trap some PCI register accesses if (Plx_pcie_is_virtfn(pPciDev)) { rc = 0; switch (offset) { case 0x00: RegValue = ((U32)pPciDev->device << 16) | pPciDev->vendor; break; case 0x08: RegValue = ((U32)pPciDev->class << 8) | pPciDev->revision; break; case 0x10: case 0x14: case 0x18: case 0x1C: case 0x20: case 0x24: // Build BAR value, accounting for 64-bit BarNum = (offset - 0x10) / sizeof(U32); RegValue = PLX_64_LOW_32( pci_resource_start( pPciDev, BarNum ) ); Flags_OS = pci_resource_flags( pPciDev, BarNum ); if (Flags_OS & IORESOURCE_IO) { RegValue |= (1 << 0); } if (Flags_OS & IORESOURCE_PREFETCH) { RegValue |= (1 << 3); } if (Flags_OS & IORESOURCE_MEM_64) { RegValue |= (2 << 1); } if ((BarNum > 0) && (pci_resource_flags( pPciDev, BarNum-1 ) & IORESOURCE_MEM_64)) { RegValue = PLX_64_HIGH_32( pci_resource_start( pPciDev, BarNum-1 ) ); } break; case 0x2C: RegValue = ((U32)pPciDev->subsystem_device << 16) | pPciDev->subsystem_vendor; break; default: rc = -1; break; } } // Shift desired bytes into position RegValue = RegValue >> (offset & 0x3); // Get read value switch (AccessSize) { case sizeof(U8): if (rc == 0) { *(U8*)pValue = (U8)RegValue; } else { rc = pci_read_config_byte( pPciDev, offset, (U8*)pValue ); } break; case sizeof(U16): if (rc == 0) { *(U16*)pValue = (U16)RegValue; } else { rc = pci_read_config_word( pPciDev, offset, (U16*)pValue ); } break; case sizeof(U32): if (rc == 0) { *(U32*)pValue = RegValue; } else { rc = pci_read_config_dword( pPciDev, offset, (U32*)pValue ); } break; default: rc = -1; break; } // Decrement reference count to device pci_dev_put( pPciDev ); if (rc != 0) { return PLX_STATUS_FAILED; } return PLX_STATUS_OK; } /******************************************************************************* * * Function : PlxPciRegWrite_ByLoc * * Description: Writes to a PCI register of a device specified by location * ******************************************************************************/ PLX_STATUS PlxPciRegWrite_ByLoc( U8 domain, U8 bus, U8 slot, U8 function, U16 offset, U32 value, U8 AccessSize ) { int rc; struct pci_dev *pPciDev; // Verify access type & offset switch (AccessSize) { case sizeof(U8): break; case sizeof(U16): if (offset & 0x1) { return PLX_STATUS_INVALID_OFFSET; } break; case sizeof(U32): if (offset & 0x3) { return PLX_STATUS_INVALID_OFFSET; } break; default: return PLX_STATUS_UNSUPPORTED; } // Locate PCI device pPciDev = Plx_pci_get_domain_bus_and_slot( domain, bus, PCI_DEVFN(slot, function) ); if (pPciDev == NULL) { DebugPrintf(( "ERROR - Device at [D%d %02X:%02X.%X] does not exist\n", domain, bus, slot, function )); return PLX_STATUS_INVALID_OBJECT; } switch (AccessSize) { case sizeof(U8): rc = pci_write_config_byte( pPciDev, offset, (U8)value ); break; case sizeof(U16): rc = pci_write_config_word( pPciDev, offset, (U16)value ); break; case sizeof(U32): rc = pci_write_config_dword( pPciDev, offset, (U32)value ); break; default: rc = -1; break; } // Decrement reference count to device pci_dev_put( pPciDev ); if (rc != 0) { return PLX_STATUS_FAILED; } return PLX_STATUS_OK; } /******************************************************************************* * * Function : PlxPciExpressRegRead * * Description: Reads a PCI Express register using ECAM * ******************************************************************************/ PLX_STATUS PlxPciExpressRegRead( U8 domain, U8 bus, U8 slot, U8 function, U16 offset, U32 *pValue ) { U64 address; // Default to error *pValue = (U32)-1; // Only PCI domain 0 currently supported if (domain != 0) { return PLX_STATUS_UNSUPPORTED; } // Offset must be on a 4-byte boundary if (offset & 0x3) { return PLX_STATUS_INVALID_OFFSET; } // Check if PCIe ECAM was probed for if (Gbl_Acpi_Addr_ECAM[2] == ACPI_PCIE_NOT_PROBED) { PlxProbeForEcamBase(); } // Check if ECAM available through ACPI if (Gbl_Acpi_Addr_ECAM[2] == ACPI_PCIE_ALWAYS_USE_OS) { return PLX_STATUS_UNSUPPORTED; } // Setup base address for register access address = ((U64)Gbl_Acpi_Addr_ECAM[1] << 32) | (Gbl_Acpi_Addr_ECAM[0] << 0) | (bus << 20) | (slot << 15) | (function << 12) | (offset << 0); // Read the register *pValue = (U32)PlxPhysicalMemRead( address, sizeof(U32) ); return PLX_STATUS_OK; } /******************************************************************************* * * Function : PlxPciExpressRegWrite * * Description: Writes a PCI Express register using ECAM * ******************************************************************************/ PLX_STATUS PlxPciExpressRegWrite( U8 domain, U8 bus, U8 slot, U8 function, U16 offset, U32 value ) { U64 address; // Only PCI domain 0 currently supported if (domain != 0) { return PLX_STATUS_UNSUPPORTED; } // Offset must be on a 4-byte boundary if (offset & 0x3) { return PLX_STATUS_INVALID_OFFSET; } // Check if PCIe ECAM was probed for if (Gbl_Acpi_Addr_ECAM[2] == ACPI_PCIE_NOT_PROBED) { PlxProbeForEcamBase(); } // Check if ECAM available through ACPI if (Gbl_Acpi_Addr_ECAM[2] == ACPI_PCIE_ALWAYS_USE_OS) { return PLX_STATUS_UNSUPPORTED; } // Setup base address for register access address = ((U64)Gbl_Acpi_Addr_ECAM[1] << 32) | (Gbl_Acpi_Addr_ECAM[0] << 0) | (bus << 20) | (slot << 15) | (function << 12) | (offset << 0); // Write the register PlxPhysicalMemWrite( address, value, sizeof(U32) ); return PLX_STATUS_OK; } /******************************************************************************* * * Function : PlxPciRegisterRead_BypassOS * * Description: Reads a PCI register by bypassing the OS services * ******************************************************************************/ PLX_STATUS PlxPciRegisterRead_BypassOS( U8 domain, U8 bus, U8 slot, U8 function, U16 offset, U32 *pValue ) { U32 RegSave; // If bypass disabled, revert to OS call if (Gbl_Acpi_Addr_ECAM[2] == ACPI_PCIE_ALWAYS_USE_OS) { return PlxPciRegRead_ByLoc( domain, bus, slot, function, offset, pValue, sizeof(U32) ); } // For PCIe extended register, use Enhanced PCIe Mechanism if (offset >= 0x100) { return PlxPciExpressRegRead( domain, bus, slot, function, offset, pValue ); } // Default to error *pValue = (U32)-1; // Only PCI domain 0 currently supported if (domain != 0) { return PLX_STATUS_UNSUPPORTED; } // Offset must be on a 4-byte boundary if (offset & 0x3) { return PLX_STATUS_INVALID_OFFSET; } /*************************************************************** * Access of a PCI register involves using I/O addresses 0xcf8 * and 0xcfc. These addresses must be used together and no other * process must interrupt. **************************************************************/ // Make sure slot is only 5-bits slot &= 0x1F; // Make sure function is only 3-bits function &= 0x7; // Save the content of the command register RegSave = IO_PORT_READ_32( 0xcf8 ); // Configure the command register to access the desired location IO_PORT_WRITE_32( 0xcf8, (1 << 31) | (bus << 16) | (slot << 11) | (function << 8) | offset ); // Read the register *pValue = IO_PORT_READ_32( 0xcfc ); // Restore the command register IO_PORT_WRITE_32( 0xcf8, RegSave ); return PLX_STATUS_OK; } /******************************************************************************* * * Function : PlxPciRegisterWrite_BypassOS * * Description: Writes to a PCI register by bypassing the OS services * ******************************************************************************/ PLX_STATUS PlxPciRegisterWrite_BypassOS( U8 domain, U8 bus, U8 slot, U8 function, U16 offset, U32 value ) { U32 RegSave; // If bypass disabled, revert to OS call if (Gbl_Acpi_Addr_ECAM[2] == ACPI_PCIE_ALWAYS_USE_OS) { return PlxPciRegWrite_ByLoc( domain, bus, slot, function, offset, value, sizeof(U32) ); } // For PCIe extended register, use Enhanced PCIe Mechanism if (offset >= 0x100) { return PlxPciExpressRegWrite( domain, bus, slot, function, offset, value ); } // Only PCI domain 0 currently supported if (domain != 0) { return PLX_STATUS_UNSUPPORTED; } // Offset must be on a 4-byte boundary if (offset & 0x3) { return PLX_STATUS_INVALID_OFFSET; } /*************************************************************** * Access of a PCI register involves using I/O addresses 0xcf8 * and 0xcfc. These addresses must be used together and no other * process must interrupt. **************************************************************/ // Make sure slot is only 5-bits slot &= 0x1F; // Make sure function is only 3-bits function &= 0x7; // Save the content of the command register RegSave = IO_PORT_READ_32( 0xcf8 ); // Configure the command register to access the desired location IO_PORT_WRITE_32( 0xcf8, (1 << 31) | (bus << 16) | (slot << 11) | (function << 8) | offset ); // Write the register IO_PORT_WRITE_32( 0xcfc, value ); // Restore the command register IO_PORT_WRITE_32( 0xcf8, RegSave ); return PLX_STATUS_OK; } /******************************************************************************* * * Function : PlxProbeForEcamBase * * Description: Probes for Enhanced Configuration Access Mechanism base * address through ACPI * ******************************************************************************/ VOID PlxProbeForEcamBase( VOID ) { U8 Str_ID[9]; U8 *pEntry; U8 *pAddress; U8 *Va_BiosRom; U8 *Va_RSDT; U8 *Va_Table; U8 *pAcpi_Addr_RSDP; U8 *pAcpi_Addr_RSDT; U16 NumEntries; U32 value; BOOLEAN bFound; ACPI_RSDT_v1_0 Acpi_Rsdt; // Do not probe again if previously did if (Gbl_Acpi_Addr_ECAM[2] != ACPI_PCIE_NOT_PROBED) { return; } // Default to ACPI and/or ECAM not detected Gbl_Acpi_Addr_ECAM[2] = ACPI_PCIE_ALWAYS_USE_OS; // Default to ECAM not found bFound = FALSE; // Initialize virtual addresses Va_BiosRom = NULL; Va_RSDT = NULL; // Mark end of string Str_ID[8] = '\0'; // Map BIOS ROM into kernel space Va_BiosRom = ioremap( BIOS_MEM_START, (BIOS_MEM_END - BIOS_MEM_START) ); if (Va_BiosRom == NULL) { goto _Exit_PlxProbeForEcamBase; } // Set physical and virtual starting addresses pAcpi_Addr_RSDP = (U8*)BIOS_MEM_START; pAddress = Va_BiosRom; // Scan system ROM for ACPI RSDP pointer do { // Read 8-bytes *(U32*)Str_ID = PHYS_MEM_READ_32( (U32*)pAddress ); *(U32*)(Str_ID + 4) = PHYS_MEM_READ_32( (U32*)(pAddress + 4) ); // Check for header signature if (memcmp( "RSD PTR ", Str_ID, 8 // 8 bytes ) == 0) { bFound = TRUE; } else { // Increment to next 16-byte boundary pAddress += 16; pAcpi_Addr_RSDP += 16; } } while (!bFound && (pAcpi_Addr_RSDP < (U8*)BIOS_MEM_END)); if (!bFound) { DebugPrintf(("ACPI Probe: ACPI not detected\n")); goto _Exit_PlxProbeForEcamBase; } // Reset flag bFound = FALSE; // Get ACPI revision value = PHYS_MEM_READ_8( (U8*)(pAddress + 15) ); DebugPrintf(( "ACPI Probe: ACPI is v%s (rev=%d)\n", (value == 0) ? "1.0" : "2.0 or higher", (int)value )); DebugPrintf(( "ACPI Probe: 'RSD PTR ' found at %p\n", pAcpi_Addr_RSDP )); // Store RSDT address pAcpi_Addr_RSDT = (U8*)PLX_INT_TO_PTR( PHYS_MEM_READ_32( (U32*)(pAddress + 16) ) ); // Map RSDT table Va_RSDT = ioremap_prot( PLX_PTR_TO_INT( pAcpi_Addr_RSDT ), 1024, 0 ); if (Va_RSDT == NULL) { goto _Exit_PlxProbeForEcamBase; } // Get RSDT size Acpi_Rsdt.Length = PHYS_MEM_READ_32( (U32*)(Va_RSDT + 4) ); if (Acpi_Rsdt.Length == 0) { DebugPrintf(("ACPI Probe: Unable to read RSDT table length\n")); goto _Exit_PlxProbeForEcamBase; } // Calculate number of entries NumEntries = (U16)((Acpi_Rsdt.Length - sizeof(ACPI_RSDT_v1_0)) / sizeof(U32)); DebugPrintf(( "ACPI Probe: RSD table at %p has %d entries\n", pAcpi_Addr_RSDT, NumEntries )); if (NumEntries > 100) { DebugPrintf(("ACPI Probe: Unable to determine number of entries in RSDT table\n")); goto _Exit_PlxProbeForEcamBase; } // Get address of first entry pEntry = Va_RSDT + sizeof(ACPI_RSDT_v1_0); // Parse entry pointers for MCFG table while (NumEntries != 0) { // Get address of entry pAddress = (U8*)PLX_INT_TO_PTR( PHYS_MEM_READ_32( (U32*)pEntry ) ); // Map table Va_Table = ioremap_prot( PLX_PTR_TO_INT( pAddress ), 200, 0 ); if (Va_Table == NULL) { goto _Exit_PlxProbeForEcamBase; } // Get table signature value = PHYS_MEM_READ_32( (U32*)Va_Table ); DebugPrintf(( "ACPI Probe: %c%c%c%c table at %p\n", (char)(value >> 0), (char)(value >> 8), (char)(value >> 16), (char)(value >> 24), pAddress )); // Check if MCFG table if (memcmp( "MCFG", &value, sizeof(U32) ) == 0) { // Get 64-bit base address of Enhanced Config Access Mechanism Gbl_Acpi_Addr_ECAM[0] = PHYS_MEM_READ_32( (U32*)(Va_Table + 44) ); Gbl_Acpi_Addr_ECAM[1] = PHYS_MEM_READ_32( (U32*)(Va_Table + 48) ); bFound = TRUE; // Flag ok to use ECAM Gbl_Acpi_Addr_ECAM[2] = ACPI_PCIE_BYPASS_OS_OK; } // Unmap table iounmap( Va_Table ); // Get address of next entry pEntry += sizeof(U32); // Decrement count NumEntries--; } _Exit_PlxProbeForEcamBase: // Release the BIOS ROM mapping if (Va_BiosRom != NULL) { iounmap( Va_BiosRom ); } // Release RSDT mapping if (Va_RSDT != NULL) { iounmap( Va_RSDT ); } if (bFound) { DebugPrintf(( "ACPI Probe: PCIe ECAM at %04X_%08X\n", (unsigned int)Gbl_Acpi_Addr_ECAM[1], (unsigned int)Gbl_Acpi_Addr_ECAM[0] )); // For newer Linux kernels, default to using OS if (LINUX_VERSION_CODE >= PLX_KER_VER_PCIE_USE_OS) { DebugPrintf(("ACPI Probe: Will default to OS for PCIe reg accesses\n")); Gbl_Acpi_Addr_ECAM[2] = ACPI_PCIE_DEFAULT_TO_OS; } } else { DebugPrintf(("ACPI Probe: MCFG entry not found (PCIe ECAM not supported)\n")); } } /******************************************************************************* * * Function : PlxPhysicalMemRead * * Description: Maps a memory location and performs a read from it * ******************************************************************************/ U64 PlxPhysicalMemRead( U64 address, U8 size ) { U64 value; VOID *KernelVa; // Map address into kernel space KernelVa = ioremap( (unsigned long)address, sizeof(U64) ); if (KernelVa == NULL) { DebugPrintf(("ERROR - Unable to map %p\n", PLX_CAST_64_TO_8_PTR(address))); return -1; } // Read memory switch (size) { case sizeof(U8): value = PHYS_MEM_READ_8( KernelVa ); break; case sizeof(U16): value = PHYS_MEM_READ_16( KernelVa ); break; case sizeof(U32): value = PHYS_MEM_READ_32( KernelVa ); break; default: value = 0; break; } // Release the mapping iounmap( KernelVa ); return value; } /******************************************************************************* * * Function : PlxPhysicalMemWrite * * Description: Maps a memory location and performs a write to it * ******************************************************************************/ U32 PlxPhysicalMemWrite( U64 address, U64 value, U8 size ) { VOID *KernelVa; // Map address into kernel space KernelVa = ioremap( (unsigned long)address, sizeof(U64) ); if (KernelVa == NULL) { DebugPrintf(("ERROR - Unable to map %p\n", PLX_CAST_64_TO_8_PTR(address))); return -1; } // Write memory switch (size) { case sizeof(U8): PHYS_MEM_WRITE_8( KernelVa, (U8)value ); break; case sizeof(U16): PHYS_MEM_WRITE_16( KernelVa, (U16)value ); break; case sizeof(U32): PHYS_MEM_WRITE_32( KernelVa, (U32)value ); break; } // Release the mapping iounmap( KernelVa ); return 0; }