/* * This file is part of the coreboot project. * * Copyright (C) 2015 Timothy Pearson , Raptor Engineering * Copyright (C) 2007 Advanced Micro Devices, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program 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 General Public License for more details. */ /* *---------------------------------------------------------------------------- * MODULES USED * *---------------------------------------------------------------------------- */ #include "h3finit.h" #include "h3ffeat.h" #include "h3ncmn.h" #include "h3gtopo.h" #include "AsPsNb.h" #include #include #include #include #include #include #include #include #include /*---------------------------------------------------------------------------- * DEFINITIONS AND MACROS * *---------------------------------------------------------------------------- */ #define NVRAM_LIMIT_HT_SPEED_200 0x12 #define NVRAM_LIMIT_HT_SPEED_300 0x11 #define NVRAM_LIMIT_HT_SPEED_400 0x10 #define NVRAM_LIMIT_HT_SPEED_500 0xf #define NVRAM_LIMIT_HT_SPEED_600 0xe #define NVRAM_LIMIT_HT_SPEED_800 0xd #define NVRAM_LIMIT_HT_SPEED_1000 0xc #define NVRAM_LIMIT_HT_SPEED_1200 0xb #define NVRAM_LIMIT_HT_SPEED_1400 0xa #define NVRAM_LIMIT_HT_SPEED_1600 0x9 #define NVRAM_LIMIT_HT_SPEED_1800 0x8 #define NVRAM_LIMIT_HT_SPEED_2000 0x7 #define NVRAM_LIMIT_HT_SPEED_2200 0x6 #define NVRAM_LIMIT_HT_SPEED_2400 0x5 #define NVRAM_LIMIT_HT_SPEED_2600 0x4 #define NVRAM_LIMIT_HT_SPEED_2800 0x3 #define NVRAM_LIMIT_HT_SPEED_3000 0x2 #define NVRAM_LIMIT_HT_SPEED_3200 0x1 #define NVRAM_LIMIT_HT_SPEED_AUTO 0x0 static const uint32_t ht_speed_limit[20] = {0xFFFFF, 0xFFFFF, 0x7FFFF, 0x3FFFF, 0x0FFFF, 0x07FFF, 0x03FFF, 0x01FFF, 0x00FFF, 0x007FF, 0x003FF, 0x001FF, 0x000FF, 0x0007F, 0x0003F, 0x0001F, 0x0000F, 0x00007, 0x00003, 0x00001}; static const struct ht_speed_limit_map_t { uint16_t mhz; uint8_t nvram; } ht_speed_limit_map[] = { {0, NVRAM_LIMIT_HT_SPEED_AUTO}, {200, NVRAM_LIMIT_HT_SPEED_200}, {300, NVRAM_LIMIT_HT_SPEED_300}, {400, NVRAM_LIMIT_HT_SPEED_400}, {500, NVRAM_LIMIT_HT_SPEED_500}, {600, NVRAM_LIMIT_HT_SPEED_600}, {800, NVRAM_LIMIT_HT_SPEED_800}, {1000, NVRAM_LIMIT_HT_SPEED_1000}, {1200, NVRAM_LIMIT_HT_SPEED_1200}, {1400, NVRAM_LIMIT_HT_SPEED_1400}, {1600, NVRAM_LIMIT_HT_SPEED_1600}, {1800, NVRAM_LIMIT_HT_SPEED_1800}, {2000, NVRAM_LIMIT_HT_SPEED_2000}, {2200, NVRAM_LIMIT_HT_SPEED_2200}, {2400, NVRAM_LIMIT_HT_SPEED_2400}, {2600, NVRAM_LIMIT_HT_SPEED_2600}, {2800, NVRAM_LIMIT_HT_SPEED_2800}, {3000, NVRAM_LIMIT_HT_SPEED_3000}, {3200, NVRAM_LIMIT_HT_SPEED_3200}, }; static const uint32_t ht_speed_mhz_to_hw(uint16_t mhz) { size_t i; for (i = 0; i < ARRAY_SIZE(ht_speed_limit_map); i++) if (ht_speed_limit_map[i].mhz == mhz) return ht_speed_limit[ht_speed_limit_map[i].nvram]; printk(BIOS_WARNING, "WARNING: Invalid HT link limit frequency %d specified, ignoring...\n", mhz); return ht_speed_limit[NVRAM_LIMIT_HT_SPEED_AUTO]; } /*---------------------------------------------------------------------------- * TYPEDEFS AND STRUCTURES * *---------------------------------------------------------------------------- */ /*---------------------------------------------------------------------------- * PROTOTYPES OF LOCAL FUNCTIONS * *---------------------------------------------------------------------------- */ /*---------------------------------------------------------------------------- * EXPORTED FUNCTIONS * *---------------------------------------------------------------------------- */ /*---------------------------------------------------------------------------- * LOCAL FUNCTIONS * *---------------------------------------------------------------------------- */ #ifndef HT_BUILD_NC_ONLY /* ************************************************************************** * Routing table decompressor ************************************************************************** */ /* ************************************************************************** * Graph Support routines * These routines provide support for dealing with the graph representation * of the topologies, along with the routing table information for that topology. * The routing information is compressed and these routines currently decompress * 'on the fly'. A graph is represented as a set of routes. All the edges in the * graph are routes; a direct route from node i to node j exists in the graph IFF * there is an edge directly connecting node i to node j. All other routes designate * the edge which the route to that node initially takes, by designating a node * to which a direct connection exists. That is, the route to non-adjacent node j * from node i specifies node k where node i directly connects to node k. * * * pseudo definition of compressed graph: * typedef struct * { * BIT broadcast[8]; * uint4 responseRoute; * uint4 requestRoute; * } sRoute; * typedef struct * { * u8 size; * sRoute graph[size][size]; * } sGraph; * ************************************************************************** */ /*---------------------------------------------------------------------------------------- * u8 * graphHowManyNodes(u8 *graph) * * Description: * Returns the number of nodes in the compressed graph * * Parameters: * @param[in] graph = a compressed graph * @param[out] results = the number of nodes in the graph * --------------------------------------------------------------------------------------- */ static u8 graphHowManyNodes(u8 *graph) { return graph[0]; } /*---------------------------------------------------------------------------------------- * BOOL * graphIsAdjacent(u8 *graph, u8 nodeA, u8 nodeB) * * Description: * Returns true if NodeA is directly connected to NodeB, false otherwise * (if NodeA == NodeB also returns false) * Relies on rule that directly connected nodes always route requests directly. * * Parameters: * @param[in] graph = the graph to examine * @param[in] nodeA = the node number of the first node * @param[in] nodeB = the node number of the second node * @param[out] results = true if nodeA connects to nodeB false if not * --------------------------------------------------------------------------------------- */ static BOOL graphIsAdjacent(u8 *graph, u8 nodeA, u8 nodeB) { u8 size = graph[0]; ASSERT(size <= MAX_NODES); ASSERT((nodeA < size) && (nodeB < size)); return (graph[1+(nodeA*size+nodeB)*2+1] & 0x0F) == nodeB; } /*---------------------------------------------------------------------------------------- * u8 * graphGetRsp(u8 *graph, u8 nodeA, u8 nodeB) * * Description: * Returns the graph node used by nodeA to route responses targeted at nodeB. * This will be a node directly connected to nodeA (possibly nodeB itself), * or "Route to Self" if nodeA and nodeB are the same node. * Note that all node numbers are abstract node numbers of the topology graph, * it is the responsibility of the caller to apply any permutation needed. * * Parameters: * @param[in] u8 graph = the graph to examine * @param[in] u8 nodeA = the node number of the first node * @param[in] u8 nodeB = the node number of the second node * @param[out] u8 results = The response route node * --------------------------------------------------------------------------------------- */ static u8 graphGetRsp(u8 *graph, u8 nodeA, u8 nodeB) { u8 size = graph[0]; ASSERT(size <= MAX_NODES); ASSERT((nodeA < size) && (nodeB < size)); return (graph[1+(nodeA*size+nodeB)*2+1] & 0xF0)>>4; } /*---------------------------------------------------------------------------------------- * u8 * graphGetReq(u8 *graph, u8 nodeA, u8 nodeB) * * Description: * Returns the graph node used by nodeA to route requests targeted at nodeB. * This will be a node directly connected to nodeA (possibly nodeB itself), * or "Route to Self" if nodeA and nodeB are the same node. * Note that all node numbers are abstract node numbers of the topology graph, * it is the responsibility of the caller to apply any permutation needed. * * Parameters: * @param[in] graph = the graph to examine * @param[in] nodeA = the node number of the first node * @param[in] nodeB = the node number of the second node * @param[out] results = The request route node * --------------------------------------------------------------------------------------- */ static u8 graphGetReq(u8 *graph, u8 nodeA, u8 nodeB) { u8 size = graph[0]; ASSERT(size <= MAX_NODES); ASSERT((nodeA < size) && (nodeB < size)); return (graph[1+(nodeA*size+nodeB)*2+1] & 0x0F); } /*---------------------------------------------------------------------------------------- * u8 * graphGetBc(u8 *graph, u8 nodeA, u8 nodeB) * * Description: * Returns a bit vector of nodes that nodeA should forward a broadcast from * nodeB towards * * Parameters: * @param[in] graph = the graph to examine * @param[in] nodeA = the node number of the first node * @param[in] nodeB = the node number of the second node * OU results = the broadcast routes for nodeA from nodeB * --------------------------------------------------------------------------------------- */ static u8 graphGetBc(u8 *graph, u8 nodeA, u8 nodeB) { u8 size = graph[0]; ASSERT(size <= MAX_NODES); ASSERT((nodeA < size) && (nodeB < size)); return graph[1+(nodeA*size+nodeB)*2]; } /*************************************************************************** *** GENERIC HYPERTRANSPORT DISCOVERY CODE *** ***************************************************************************/ /*---------------------------------------------------------------------------------------- * void * routeFromBSP(u8 targetNode, u8 actualTarget, sMainData *pDat) * * Description: * Ensure a request / response route from target node to bsp. Since target node is * always a predecessor of actual target node, each node gets a route to actual target * on the link that goes to target. The routing produced by this routine is adequate * for config access during discovery, but NOT for coherency. * * Parameters: * @param[in] u8 targetNode = the path to actual target goes through target * @param[in] u8 actualTarget = the ultimate target being routed to * @param[in] sMainData* pDat = our global state, port config info * --------------------------------------------------------------------------------------- */ static void routeFromBSP(u8 targetNode, u8 actualTarget, sMainData *pDat) { u8 predecessorNode, predecessorLink, currentPair; if (targetNode == 0) return; /* BSP has no predecessor, stop */ /* Search for the link that connects targetNode to its predecessor */ currentPair = 0; while (pDat->PortList[currentPair*2+1].NodeID != targetNode) { currentPair++; ASSERT(currentPair < pDat->TotalLinks); } predecessorNode = pDat->PortList[currentPair*2].NodeID; predecessorLink = pDat->PortList[currentPair*2].Link; /* Recursively call self to ensure the route from the BSP to the Predecessor */ /* Node is established */ routeFromBSP(predecessorNode, actualTarget, pDat); pDat->nb->writeRoutingTable(predecessorNode, actualTarget, predecessorLink, pDat->nb); } /*---------------------------------------------------------------------------*/ /** * u8 * convertNodeToLink(u8 srcNode, u8 targetNode, sMainData *pDat) * * Description: * Return the link on source node which connects to target node * * Parameters: * @param[in] srcNode = the source node * @param[in] targetNode = the target node to find the link to * @param[in] pDat = our global state * @return the link on source which connects to target * */ static u8 convertNodeToLink(u8 srcNode, u8 targetNode, sMainData *pDat) { u8 targetlink = INVALID_LINK; u8 k; for (k = 0; k < pDat->TotalLinks*2; k += 2) { if ((pDat->PortList[k+0].NodeID == srcNode) && (pDat->PortList[k+1].NodeID == targetNode)) { targetlink = pDat->PortList[k+0].Link; break; } else if ((pDat->PortList[k+1].NodeID == srcNode) && (pDat->PortList[k+0].NodeID == targetNode)) { targetlink = pDat->PortList[k+1].Link; break; } } ASSERT(targetlink != INVALID_LINK); return targetlink; } /*---------------------------------------------------------------------------------------- * void * htDiscoveryFloodFill(sMainData *pDat) * * Description: * Discover all coherent devices in the system, initializing some basics like node IDs * and total nodes found in the process. As we go we also build a representation of the * discovered system which we will use later to program the routing tables. During this * step, the routing is via default link back to BSP and to each new node on the link it * was discovered on (no coherency is active yet). * * Parameters: * @param[in] sMainData* pDat = our global state * --------------------------------------------------------------------------------------- */ static void htDiscoveryFloodFill(sMainData *pDat) { uint8_t currentNode = 0; uint8_t currentLink; uint8_t currentLinkID; /* NOTE * Each node inside a dual node (socket G34) processor must share * an adjacent node ID. Alter the link scan order such that the * other internal node is always scanned first... */ uint8_t currentLinkScanOrder_Default[8] = {0, 1, 2, 3, 4, 5, 6, 7}; uint8_t currentLinkScanOrder_G34_Fam10[8] = {1, 0, 2, 3, 4, 5, 6, 7}; uint8_t currentLinkScanOrder_G34_Fam15[8] = {2, 0, 1, 3, 4, 5, 6, 7}; uint8_t fam15h = 0; uint8_t rev_gte_d = 0; uint8_t dual_node = 0; uint32_t f3xe8; uint32_t family; uint32_t model; f3xe8 = pci_read_config32(NODE_PCI(0, 3), 0xe8); family = model = cpuid_eax(0x80000001); model = ((model & 0xf0000) >> 12) | ((model & 0xf0) >> 4); family = ((family & 0xf00000) >> 16) | ((family & 0xf00) >> 8); if (family >= 0x6f) { /* Family 15h or later */ fam15h = 1; } if ((model >= 0x8) || fam15h) /* Revision D or later */ rev_gte_d = 1; if (rev_gte_d) /* Check for dual node capability */ if (f3xe8 & 0x20000000) dual_node = 1; /* Entries are always added in pairs, the even indices are the 'source' * side closest to the BSP, the odd indices are the 'destination' side */ while (currentNode <= pDat->NodesDiscovered) { u32 temp; if (currentNode != 0) { /* Set path from BSP to currentNode */ routeFromBSP(currentNode, currentNode, pDat); /* Set path from BSP to currentNode for currentNode+1 if * currentNode+1 != MAX_NODES */ if (currentNode+1 != MAX_NODES) routeFromBSP(currentNode, currentNode+1, pDat); /* Configure currentNode to route traffic to the BSP through its * default link */ pDat->nb->writeRoutingTable(currentNode, 0, pDat->nb->readDefLnk(currentNode, pDat->nb), pDat->nb); } /* Set currentNode's NodeID field to currentNode */ pDat->nb->writeNodeID(currentNode, currentNode, pDat->nb); /* Enable routing tables on currentNode */ pDat->nb->enableRoutingTables(currentNode, pDat->nb); for (currentLinkID = 0; currentLinkID < pDat->nb->maxLinks; currentLinkID++) { BOOL linkfound; u8 token; if (currentLinkID < 8) { if (dual_node) { if (fam15h) currentLink = currentLinkScanOrder_G34_Fam15[currentLinkID]; else currentLink = currentLinkScanOrder_G34_Fam10[currentLinkID]; } else { currentLink = currentLinkScanOrder_Default[currentLinkID]; } } else { currentLink = currentLinkID; } if (pDat->HtBlock->AMD_CB_IgnoreLink && pDat->HtBlock->AMD_CB_IgnoreLink(currentNode, currentLink)) continue; if (pDat->nb->readTrueLinkFailStatus(currentNode, currentLink, pDat, pDat->nb)) continue; /* Make sure that the link is connected, coherent, and ready */ if (!pDat->nb->verifyLinkIsCoherent(currentNode, currentLink, pDat->nb)) continue; /* Test to see if the currentLink has already been explored */ linkfound = FALSE; for (temp = 0; temp < pDat->TotalLinks; temp++) { if ((pDat->PortList[temp*2+1].NodeID == currentNode) && (pDat->PortList[temp*2+1].Link == currentLink)) { linkfound = TRUE; break; } } if (linkfound) { /* We had already expored this link */ continue; } if (pDat->nb->handleSpecialLinkCase(currentNode, currentLink, pDat, pDat->nb)) { continue; } /* Modify currentNode's routing table to use currentLink to send * traffic to currentNode+1 */ pDat->nb->writeRoutingTable(currentNode, currentNode+1, currentLink, pDat->nb); /* Check the northbridge of the node we just found, to make sure it is compatible * before doing anything else to it. */ if (!pDat->nb->isCompatible(currentNode+1, pDat->nb)) { u8 nodeToKill; /* Notify BIOS of event (while variables are still the same) */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventCohFamilyFeud evt; evt.eSize = sizeof(sHtEventCohFamilyFeud); evt.node = currentNode; evt.link = currentLink; evt.totalNodes = pDat->NodesDiscovered; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR, HT_EVENT_COH_FAMILY_FEUD, (u8 *)&evt); } /* If node is not compatible, force boot to 1P * If they are not compatible stop cHT init and: * 1. Disable all cHT links on the BSP * 2. Configure the BSP routing tables as a UP. * 3. Notify main BIOS. */ pDat->NodesDiscovered = 0; currentNode = 0; pDat->TotalLinks = 0; /* Abandon our coherent link data structure. At this point there may * be coherent links on the BSP that are not yet in the portList, and * we have to turn them off anyway. So depend on the hardware to tell us. */ for (currentLink = 0; currentLink < pDat->nb->maxLinks; currentLink++) { /* Stop all links which are connected, coherent, and ready */ if (pDat->nb->verifyLinkIsCoherent(currentNode, currentLink, pDat->nb)) pDat->nb->stopLink(currentNode, currentLink, pDat->nb); } for (nodeToKill = 0; nodeToKill < pDat->nb->maxNodes; nodeToKill++) { pDat->nb->writeFullRoutingTable(0, nodeToKill, ROUTETOSELF, ROUTETOSELF, 0, pDat->nb); } /* End Coherent Discovery */ STOP_HERE; break; } /* Read token from Current+1 */ token = pDat->nb->readToken(currentNode+1, pDat->nb); ASSERT(token <= pDat->NodesDiscovered); if (token == 0) { pDat->NodesDiscovered++; ASSERT(pDat->NodesDiscovered < pDat->nb->maxNodes); /* Check the capability of northbridges against the currently known configuration */ if (!pDat->nb->isCapable(currentNode+1, pDat, pDat->nb)) { u8 nodeToKill; /* Notify BIOS of event */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventCohMpCapMismatch evt; evt.eSize = sizeof(sHtEventCohMpCapMismatch); evt.node = currentNode; evt.link = currentLink; evt.sysMpCap = pDat->sysMpCap; evt.totalNodes = pDat->NodesDiscovered; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR, HT_EVENT_COH_MPCAP_MISMATCH, (u8 *)&evt); } pDat->NodesDiscovered = 0; currentNode = 0; pDat->TotalLinks = 0; for (nodeToKill = 0; nodeToKill < pDat->nb->maxNodes; nodeToKill++) { pDat->nb->writeFullRoutingTable(0, nodeToKill, ROUTETOSELF, ROUTETOSELF, 0, pDat->nb); } /* End Coherent Discovery */ STOP_HERE; break; } token = pDat->NodesDiscovered; pDat->nb->writeToken(currentNode+1, token, pDat->nb); /* Inform that we have discovered a node, so that logical id to * socket mapping info can be recorded. */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventCohNodeDiscovered evt; evt.eSize = sizeof(sHtEventCohNodeDiscovered); evt.node = currentNode; evt.link = currentLink; evt.newNode = token; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_INFO, HT_EVENT_COH_NODE_DISCOVERED, (u8 *)&evt); } } if (pDat->TotalLinks == MAX_PLATFORM_LINKS) { /* * Exceeded our capacity to describe all coherent links found in the system. * Error strategy: * Auto recovery is not possible because data space is already all used. * If the callback is not implemented or returns we will continue to initialize * the fabric we are capable of representing, adding no more nodes or links. * This should yield a bootable topology, but likely not the one intended. * We cannot continue discovery, there may not be any way to route a new * node back to the BSP if we can't add links to our representation of the system. */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventCohLinkExceed evt; evt.eSize = sizeof(sHtEventCohLinkExceed); evt.node = currentNode; evt.link = currentLink; evt.targetNode = token; evt.totalNodes = pDat->NodesDiscovered; evt.maxLinks = pDat->nb->maxLinks; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR, HT_EVENT_COH_LINK_EXCEED, (u8 *)&evt); } /* Force link and node loops to halt */ STOP_HERE; currentNode = pDat->NodesDiscovered; break; } pDat->PortList[pDat->TotalLinks*2].Type = PORTLIST_TYPE_CPU; pDat->PortList[pDat->TotalLinks*2].Link = currentLink; pDat->PortList[pDat->TotalLinks*2].NodeID = currentNode; pDat->PortList[pDat->TotalLinks*2+1].Type = PORTLIST_TYPE_CPU; pDat->PortList[pDat->TotalLinks*2+1].Link = pDat->nb->readDefLnk(currentNode+1, pDat->nb); pDat->PortList[pDat->TotalLinks*2+1].NodeID = token; pDat->TotalLinks++; if (!pDat->sysMatrix[currentNode][token]) { pDat->sysDegree[currentNode]++; pDat->sysDegree[token]++; pDat->sysMatrix[currentNode][token] = TRUE; pDat->sysMatrix[token][currentNode] = TRUE; } } currentNode++; } } /*************************************************************************** *** ISOMORPHISM BASED ROUTING TABLE GENERATION CODE *** ***************************************************************************/ /*---------------------------------------------------------------------------------------- * BOOL * isoMorph(u8 i, sMainData *pDat) * * Description: * Is graphA isomorphic to graphB? * if this function returns true, then Perm will contain the permutation * required to transform graphB into graphA. * We also use the degree of each node, that is the number of connections it has, to * speed up rejection of non-isomorphic graphs (if there is a node in graphA with n * connections, there must be at least one unmatched in graphB with n connections). * * Parameters: * @param[in] u8 i = the discovered node which we are trying to match * with a permutation the topology * @param[in]/@param[out] sMainData* pDat = our global state, degree and adjacency matrix, * output a permutation if successful * @param[out] BOOL results = the graphs are (or are not) isomorphic * --------------------------------------------------------------------------------------- */ static BOOL isoMorph(u8 i, sMainData *pDat) { u8 j, k; u8 nodecnt; /* We have only been called if nodecnt == pSelected->size ! */ nodecnt = pDat->NodesDiscovered+1; if (i != nodecnt) { /* Keep building the permutation */ for (j = 0; j < nodecnt; j++) { /* Make sure the degree matches */ if (pDat->sysDegree[i] != pDat->dbDegree[j]) continue; /* Make sure that j hasn't been used yet (ought to use a "used" */ /* array instead, might be faster) */ for (k = 0; k < i; k++) { if (pDat->Perm[k] == j) break; } if (k != i) continue; pDat->Perm[i] = j; if (isoMorph(i+1, pDat)) return TRUE; } return FALSE; } else { /* Test to see if the permutation is isomorphic */ for (j = 0; j < nodecnt; j++) { for (k = 0; k < nodecnt; k++) { if (pDat->sysMatrix[j][k] != pDat->dbMatrix[pDat->Perm[j]][pDat->Perm[k]]) return FALSE; } } return TRUE; } } /*---------------------------------------------------------------------------------------- * void * lookupComputeAndLoadRoutingTables(sMainData *pDat) * * Description: * Using the description of the fabric topology we discovered, try to find a match * among the supported topologies. A supported topology description matches * the discovered fabric if the nodes can be matched in such a way that all the nodes connected * in one set are exactly the nodes connected in the other (formally, that the graphs are * isomorphic). Which links are used is not really important to matching. If the graphs * match, then there is a permutation of one that translates the node positions and linkages * to the other. * * In order to make the isomorphism test efficient, we test for matched number of nodes * (a 4 node fabric is not isomorphic to a 2 node topology), and provide degrees of nodes * to the isomorphism test. * * The generic routing table solution for any topology is predetermined and represented * as part of the topology. The permutation we computed tells us how to interpret the * routing onto the fabric we discovered. We do this working backward from the last * node discovered to the BSP, writing the routing tables as we go. * * Parameters: * @param[in] sMainData* pDat = our global state, the discovered fabric, * @param[out] degree matrix, permutation * --------------------------------------------------------------------------------------- */ static void lookupComputeAndLoadRoutingTables(sMainData *pDat) { u8 **pTopologyList; u8 *pSelected; int i, j, k, size; size = pDat->NodesDiscovered + 1; /* Use the provided topology list or the internal, default one. */ pTopologyList = pDat->HtBlock->topolist; if (pTopologyList == NULL) { getAmdTopolist(&pTopologyList); } pSelected = *pTopologyList; while (pSelected != NULL) { if (graphHowManyNodes(pSelected) == size) { /* Build Degree vector and Adjency Matrix for this entry */ for (i = 0; i < size; i++) { pDat->dbDegree[i] = 0; for (j = 0; j < size; j++) { if (graphIsAdjacent(pSelected, i, j)) { pDat->dbMatrix[i][j] = 1; pDat->dbDegree[i]++; } else { pDat->dbMatrix[i][j] = 0; } } } if (isoMorph(0, pDat)) break; /* A matching topology was found */ } pTopologyList++; pSelected = *pTopologyList; } if (pSelected != NULL) { /* Compute the reverse Permutation */ for (i = 0; i < size; i++) { pDat->ReversePerm[pDat->Perm[i]] = i; } /* Start with the last discovered node, and move towards the BSP */ for (i = size-1; i >= 0; i--) { for (j = 0; j < size; j++) { u8 ReqTargetLink, RspTargetLink; u8 ReqTargetNode, RspTargetNode; u8 AbstractBcTargetNodes = graphGetBc(pSelected, pDat->Perm[i], pDat->Perm[j]); u32 BcTargetLinks = 0; for (k = 0; k < MAX_NODES; k++) { if (AbstractBcTargetNodes & ((u32)1<ReversePerm[k], pDat); } } if (i == j) { ReqTargetLink = ROUTETOSELF; RspTargetLink = ROUTETOSELF; } else { ReqTargetNode = graphGetReq(pSelected, pDat->Perm[i], pDat->Perm[j]); ReqTargetLink = convertNodeToLink(i, pDat->ReversePerm[ReqTargetNode], pDat); RspTargetNode = graphGetRsp(pSelected, pDat->Perm[i], pDat->Perm[j]); RspTargetLink = convertNodeToLink(i, pDat->ReversePerm[RspTargetNode], pDat); } pDat->nb->writeFullRoutingTable(i, j, ReqTargetLink, RspTargetLink, BcTargetLinks, pDat->nb); } /* Clean up discovery 'footprint' that otherwise remains in the routing table. It didn't hurt * anything, but might cause confusion during debug and validation. Do this by setting the * route back to all self routes. Since it's the node that would be one more than actually installed, * this only applies if less than maxNodes were found. */ if (size < pDat->nb->maxNodes) { pDat->nb->writeFullRoutingTable(i, size, ROUTETOSELF, ROUTETOSELF, 0, pDat->nb); } } } else { /* * No Matching Topology was found * Error Strategy: * Auto recovery doesn't seem likely, Force boot as 1P. * For reporting, logging, provide number of nodes * If not implemented or returns, boot as BSP uniprocessor. */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventCohNoTopology evt; evt.eSize = sizeof(sHtEventCohNoTopology); evt.totalNodes = pDat->NodesDiscovered; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR, HT_EVENT_COH_NO_TOPOLOGY, (u8 *)&evt); } STOP_HERE; /* Force 1P */ pDat->NodesDiscovered = 0; pDat->TotalLinks = 0; pDat->nb->enableRoutingTables(0, pDat->nb); } } #endif /* HT_BUILD_NC_ONLY */ /*---------------------------------------------------------------------------------------- * void * finializeCoherentInit(sMainData *pDat) * * Description: * Find the total number of cores and update the number of nodes and cores in all cpus. * Limit CPU config access to installed cpus. * * Parameters: * @param[in] sMainData* pDat = our global state, number of nodes discovered. * --------------------------------------------------------------------------------------- */ static void finializeCoherentInit(sMainData *pDat) { u8 curNode; u8 totalCores = 0; for (curNode = 0; curNode < pDat->NodesDiscovered+1; curNode++) { totalCores += pDat->nb->getNumCoresOnNode(curNode, pDat->nb); } for (curNode = 0; curNode < pDat->NodesDiscovered+1; curNode++) { pDat->nb->setTotalNodesAndCores(curNode, pDat->NodesDiscovered+1, totalCores, pDat->nb); } for (curNode = 0; curNode < pDat->NodesDiscovered+1; curNode++) { pDat->nb->limitNodes(curNode, pDat->nb); } } /*---------------------------------------------------------------------------------------- * void * coherentInit(sMainData *pDat) * * Description: * Perform discovery and initialization of the coherent fabric. * * Parameters: * @param[in] sMainData* pDat = our global state * --------------------------------------------------------------------------------------- */ static void coherentInit(sMainData *pDat) { #ifdef HT_BUILD_NC_ONLY /* Replace discovery process with: * No other nodes, no coherent links * Enable routing tables on currentNode, for power on self route */ pDat->NodesDiscovered = 0; pDat->TotalLinks = 0; pDat->nb->enableRoutingTables(0, pDat->nb); #else u8 i, j; pDat->NodesDiscovered = 0; pDat->TotalLinks = 0; for (i = 0; i < MAX_NODES; i++) { pDat->sysDegree[i] = 0; for (j = 0; j < MAX_NODES; j++) { pDat->sysMatrix[i][j] = 0; } } htDiscoveryFloodFill(pDat); lookupComputeAndLoadRoutingTables(pDat); #endif finializeCoherentInit(pDat); } /*************************************************************************** *** Non-coherent init code *** *** Algorithms *** ***************************************************************************/ /*---------------------------------------------------------------------------------------- * void * processLink(u8 node, u8 link, sMainData *pDat) * * Description: * Process a non-coherent link, enabling a range of bus numbers, and setting the device * ID for all devices found * * Parameters: * @param[in] u8 node = Node on which to process nc init * @param[in] u8 link = The non-coherent link on that node * @param[in] sMainData* pDat = our global state * --------------------------------------------------------------------------------------- */ static void processLink(u8 node, u8 link, sMainData *pDat) { u8 secBus, subBus; u32 currentBUID; u32 temp; u32 unitIDcnt; SBDFO currentPtr; u8 depth; const u8 *pSwapPtr; SBDFO lastSBDFO = ILLEGAL_SBDFO; u8 lastLink = 0; ASSERT(node < pDat->nb->maxNodes && link < pDat->nb->maxLinks); if ((pDat->HtBlock->AMD_CB_OverrideBusNumbers == NULL) || !pDat->HtBlock->AMD_CB_OverrideBusNumbers(node, link, &secBus, &subBus)) { /* Assign Bus numbers */ if (pDat->AutoBusCurrent >= pDat->HtBlock->AutoBusMax) { /* If we run out of Bus Numbers notify, if call back unimplemented or if it * returns, skip this chain */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHTEventNcohBusMaxExceed evt; evt.eSize = sizeof(sHTEventNcohBusMaxExceed); evt.node = node; evt.link = link; evt.bus = pDat->AutoBusCurrent; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR,HT_EVENT_NCOH_BUS_MAX_EXCEED,(u8 *)&evt); } STOP_HERE; return; } if (pDat->UsedCfgMapEntires >= 4) { /* If we have used all the PCI Config maps we can't add another chain. * Notify and if call back is unimplemented or returns, skip this chain. */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventNcohCfgMapExceed evt; evt.eSize = sizeof(sHtEventNcohCfgMapExceed); evt.node = node; evt.link = link; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR, HT_EVENT_NCOH_CFG_MAP_EXCEED, (u8 *)&evt); } STOP_HERE; return; } secBus = pDat->AutoBusCurrent; subBus = secBus + pDat->HtBlock->AutoBusIncrement-1; pDat->AutoBusCurrent += pDat->HtBlock->AutoBusIncrement; } pDat->nb->setCFGAddrMap(pDat->UsedCfgMapEntires, secBus, subBus, node, link, pDat, pDat->nb); pDat->UsedCfgMapEntires++; if ((pDat->HtBlock->AMD_CB_ManualBUIDSwapList != NULL) && pDat->HtBlock->AMD_CB_ManualBUIDSwapList(node, link, &pSwapPtr)) { /* Manual non-coherent BUID assignment */ currentBUID = 1; /* Assign BUID's per manual override */ while (*pSwapPtr != 0xFF) { currentPtr = MAKE_SBDFO(0, secBus, *pSwapPtr, 0, 0); pSwapPtr++; do { AmdPCIFindNextCap(¤tPtr); ASSERT(currentPtr != ILLEGAL_SBDFO); AmdPCIRead(currentPtr, &temp); } while (!IS_HT_SLAVE_CAPABILITY(temp)); currentBUID = *pSwapPtr; pSwapPtr++; AmdPCIWriteBits(currentPtr, 20, 16, ¤tBUID); } /* Build chain of devices */ depth = 0; pSwapPtr++; while (*pSwapPtr != 0xFF) { pDat->PortList[pDat->TotalLinks*2].NodeID = node; if (depth == 0) { pDat->PortList[pDat->TotalLinks*2].Type = PORTLIST_TYPE_CPU; pDat->PortList[pDat->TotalLinks*2].Link = link; } else { pDat->PortList[pDat->TotalLinks*2].Type = PORTLIST_TYPE_IO; pDat->PortList[pDat->TotalLinks*2].Link = 1-lastLink; pDat->PortList[pDat->TotalLinks*2].HostLink = link; pDat->PortList[pDat->TotalLinks*2].HostDepth = depth-1; pDat->PortList[pDat->TotalLinks*2].Pointer = lastSBDFO; } pDat->PortList[pDat->TotalLinks*2+1].Type = PORTLIST_TYPE_IO; pDat->PortList[pDat->TotalLinks*2+1].NodeID = node; pDat->PortList[pDat->TotalLinks*2+1].HostLink = link; pDat->PortList[pDat->TotalLinks*2+1].HostDepth = depth; currentPtr = MAKE_SBDFO(0, secBus, (*pSwapPtr & 0x3F), 0, 0); do { AmdPCIFindNextCap(¤tPtr); ASSERT(currentPtr != ILLEGAL_SBDFO); AmdPCIRead(currentPtr, &temp); } while (!IS_HT_SLAVE_CAPABILITY(temp)); pDat->PortList[pDat->TotalLinks*2+1].Pointer = currentPtr; lastSBDFO = currentPtr; /* Bit 6 indicates whether orientation override is desired. * Bit 7 indicates the upstream link if overriding. */ /* assert catches at least the one known incorrect setting */ ASSERT ((*pSwapPtr & 0x40) || (!(*pSwapPtr & 0x80))); if (*pSwapPtr & 0x40) { /* Override the device's orientation */ lastLink = *pSwapPtr >> 7; } else { /* Detect the device's orientation */ AmdPCIReadBits(currentPtr, 26, 26, &temp); lastLink = (u8)temp; } pDat->PortList[pDat->TotalLinks*2+1].Link = lastLink; depth++; pDat->TotalLinks++; pSwapPtr++; } } else { /* Automatic non-coherent device detection */ depth = 0; currentBUID = 1; while (1) { currentPtr = MAKE_SBDFO(0, secBus, 0, 0, 0); AmdPCIRead(currentPtr, &temp); if (temp == 0xFFFFFFFF) /* No device found at currentPtr */ break; if (pDat->TotalLinks == MAX_PLATFORM_LINKS) { /* * Exceeded our capacity to describe all non-coherent links found in the system. * Error strategy: * Auto recovery is not possible because data space is already all used. */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventNcohLinkExceed evt; evt.eSize = sizeof(sHtEventNcohLinkExceed); evt.node = node; evt.link = link; evt.depth = depth; evt.maxLinks = pDat->nb->maxLinks; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR, HT_EVENT_NCOH_LINK_EXCEED, (u8 *)&evt); } /* Force link loop to halt */ STOP_HERE; break; } pDat->PortList[pDat->TotalLinks*2].NodeID = node; if (depth == 0) { pDat->PortList[pDat->TotalLinks*2].Type = PORTLIST_TYPE_CPU; pDat->PortList[pDat->TotalLinks*2].Link = link; } else { pDat->PortList[pDat->TotalLinks*2].Type = PORTLIST_TYPE_IO; pDat->PortList[pDat->TotalLinks*2].Link = 1-lastLink; pDat->PortList[pDat->TotalLinks*2].HostLink = link; pDat->PortList[pDat->TotalLinks*2].HostDepth = depth-1; pDat->PortList[pDat->TotalLinks*2].Pointer = lastSBDFO; } pDat->PortList[pDat->TotalLinks*2+1].Type = PORTLIST_TYPE_IO; pDat->PortList[pDat->TotalLinks*2+1].NodeID = node; pDat->PortList[pDat->TotalLinks*2+1].HostLink = link; pDat->PortList[pDat->TotalLinks*2+1].HostDepth = depth; do { AmdPCIFindNextCap(¤tPtr); ASSERT(currentPtr != ILLEGAL_SBDFO); AmdPCIRead(currentPtr, &temp); } while (!IS_HT_SLAVE_CAPABILITY(temp)); AmdPCIReadBits(currentPtr, 25, 21, &unitIDcnt); if ((unitIDcnt + currentBUID > 31) || ((secBus == 0) && (unitIDcnt + currentBUID > 24))) { /* An error handler for the case where we run out of BUID's on a chain */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventNcohBuidExceed evt; evt.eSize = sizeof(sHtEventNcohBuidExceed); evt.node = node; evt.link = link; evt.depth = depth; evt.currentBUID = (uint8)currentBUID; evt.unitCount = (uint8)unitIDcnt; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR,HT_EVENT_NCOH_BUID_EXCEED,(u8 *)&evt); } STOP_HERE; break; } AmdPCIWriteBits(currentPtr, 20, 16, ¤tBUID); currentPtr += MAKE_SBDFO(0, 0, currentBUID, 0, 0); AmdPCIReadBits(currentPtr, 20, 16, &temp); if (temp != currentBUID) { /* An error handler for this critical error */ if (pDat->HtBlock->AMD_CB_EventNotify) { sHtEventNcohDeviceFailed evt; evt.eSize = sizeof(sHtEventNcohDeviceFailed); evt.node = node; evt.link = link; evt.depth = depth; evt.attemptedBUID = (uint8)currentBUID; pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_ERROR,HT_EVENT_NCOH_DEVICE_FAILED,(u8 *)&evt); } STOP_HERE; break; } AmdPCIReadBits(currentPtr, 26, 26, &temp); pDat->PortList[pDat->TotalLinks*2+1].Link = (u8)temp; pDat->PortList[pDat->TotalLinks*2+1].Pointer = currentPtr; lastLink = (u8)temp; lastSBDFO = currentPtr; depth++; pDat->TotalLinks++; currentBUID += unitIDcnt; } if (pDat->HtBlock->AMD_CB_EventNotify) { /* Provide information on automatic device results */ sHtEventNcohAutoDepth evt; evt.eSize = sizeof(sHtEventNcohAutoDepth); evt.node = node; evt.link = link; evt.depth = (depth - 1); pDat->HtBlock->AMD_CB_EventNotify(HT_EVENT_CLASS_INFO,HT_EVENT_NCOH_AUTO_DEPTH,(u8 *)&evt); } } } /*---------------------------------------------------------------------------------------- * void * ncInit(sMainData *pDat) * * Description: * Initialize the non-coherent fabric. Begin with the compat link on the BSP, then * find and initialize all other non-coherent chains. * * Parameters: * @param[in] sMainData* pDat = our global state * --------------------------------------------------------------------------------------- */ static void ncInit(sMainData *pDat) { u8 node, link; u8 compatLink; compatLink = pDat->nb->readSbLink(pDat->nb); processLink(0, compatLink, pDat); for (node = 0; node <= pDat->NodesDiscovered; node++) { for (link = 0; link < pDat->nb->maxLinks; link++) { if (pDat->HtBlock->AMD_CB_IgnoreLink && pDat->HtBlock->AMD_CB_IgnoreLink(node, link)) continue; /* Skip the link */ if (node == 0 && link == compatLink) continue; if (pDat->nb->readTrueLinkFailStatus(node, link, pDat, pDat->nb)) continue; if (pDat->nb->verifyLinkIsNonCoherent(node, link, pDat->nb)) processLink(node, link, pDat); } } } /*************************************************************************** *** Link Optimization *** ***************************************************************************/ /*---------------------------------------------------------------------------------------- * void * regangLinks(sMainData *pDat) * * Description: * Test the sublinks of a link to see if they qualify to be reganged. If they do, * update the port list data to indicate that this should be done. Note that no * actual hardware state is changed in this routine. * * Parameters: * @param[in,out] sMainData* pDat = our global state * --------------------------------------------------------------------------------------- */ static void regangLinks(sMainData *pDat) { #ifndef HT_BUILD_NC_ONLY u8 i, j; for (i = 0; i < pDat->TotalLinks*2; i += 2) { ASSERT(pDat->PortList[i].Type < 2 && pDat->PortList[i].Link < pDat->nb->maxLinks); /* Data validation */ ASSERT(pDat->PortList[i+1].Type < 2 && pDat->PortList[i+1].Link < pDat->nb->maxLinks); /* data validation */ ASSERT(!(pDat->PortList[i].Type == PORTLIST_TYPE_IO && pDat->PortList[i+1].Type == PORTLIST_TYPE_CPU)); /* ensure src is closer to the bsp than dst */ /* Regang is false unless we pass all conditions below */ pDat->PortList[i].SelRegang = FALSE; pDat->PortList[i+1].SelRegang = FALSE; if ((pDat->PortList[i].Type != PORTLIST_TYPE_CPU) || (pDat->PortList[i+1].Type != PORTLIST_TYPE_CPU)) continue; /* Only process CPU to CPU links */ for (j = i+2; j < pDat->TotalLinks*2; j += 2) { if ((pDat->PortList[j].Type != PORTLIST_TYPE_CPU) || (pDat->PortList[j+1].Type != PORTLIST_TYPE_CPU)) continue; /* Only process CPU to CPU links */ if (pDat->PortList[i].NodeID != pDat->PortList[j].NodeID) continue; /* Links must be from the same source */ if (pDat->PortList[i+1].NodeID != pDat->PortList[j+1].NodeID) continue; /* Link must be to the same target */ if ((pDat->PortList[i].Link & 3) != (pDat->PortList[j].Link & 3)) continue; /* Ensure same source base port */ if ((pDat->PortList[i+1].Link & 3) != (pDat->PortList[j+1].Link & 3)) continue; /* Ensure same destination base port */ if ((pDat->PortList[i].Link & 4) != (pDat->PortList[i+1].Link & 4)) continue; /* Ensure sublink0 routes to sublink0 */ ASSERT((pDat->PortList[j].Link & 4) == (pDat->PortList[j+1].Link & 4)); /* (therefore sublink1 routes to sublink1) */ if (pDat->HtBlock->AMD_CB_SkipRegang && pDat->HtBlock->AMD_CB_SkipRegang(pDat->PortList[i].NodeID, pDat->PortList[i].Link & 0x03, pDat->PortList[i+1].NodeID, pDat->PortList[i+1].Link & 0x03)) { continue; /* Skip regang */ } pDat->PortList[i].Link &= 0x03; /* Force to point to sublink0 */ pDat->PortList[i+1].Link &= 0x03; pDat->PortList[i].SelRegang = TRUE; /* Enable link reganging */ pDat->PortList[i+1].SelRegang = TRUE; pDat->PortList[i].PrvWidthOutCap = HT_WIDTH_16_BITS; pDat->PortList[i+1].PrvWidthOutCap = HT_WIDTH_16_BITS; pDat->PortList[i].PrvWidthInCap = HT_WIDTH_16_BITS; pDat->PortList[i+1].PrvWidthInCap = HT_WIDTH_16_BITS; /* Delete PortList[j, j+1], slow but easy to debug implementation */ pDat->TotalLinks--; Amdmemcpy(&(pDat->PortList[j]), &(pDat->PortList[j+2]), sizeof(sPortDescriptor)*(pDat->TotalLinks*2-j)); Amdmemset(&(pDat->PortList[pDat->TotalLinks*2]), INVALID_LINK, sizeof(sPortDescriptor)*2); /* //High performance, but would make debuging harder due to 'shuffling' of the records */ /* //Amdmemcpy(PortList[TotalPorts-2], PortList[j], SIZEOF(sPortDescriptor)*2); */ /* //TotalPorts -=2; */ break; /* Exit loop, advance to PortList[i+2] */ } } #endif /* HT_BUILD_NC_ONLY */ } static void detectIoLinkIsochronousCapable(sMainData *pDat) { uint8_t i; unsigned char iommu; uint8_t isochronous_capable = 0; iommu = 1; get_option(&iommu, "iommu"); for (i = 0; i < pDat->TotalLinks*2; i += 2) { if ((pDat->PortList[i].Type == PORTLIST_TYPE_CPU) && (pDat->PortList[i+1].Type == PORTLIST_TYPE_IO)) { if ((pDat->PortList[i].PrvFeatureCap & 0x1) && (pDat->PortList[i+1].PrvFeatureCap & 0x1)) { pDat->PortList[i].enable_isochronous_mode = 1; pDat->PortList[i+1].enable_isochronous_mode = 1; isochronous_capable = 1; } else { pDat->PortList[i].enable_isochronous_mode = 0; pDat->PortList[i+1].enable_isochronous_mode = 0; } } } if (isochronous_capable && iommu) { printk(BIOS_DEBUG, "Forcing HT links to isochronous mode due to enabled IOMMU\n"); /* Isochronous mode must be set on all links if the IOMMU is enabled */ for (i = 0; i < pDat->TotalLinks*2; i += 2) { pDat->PortList[i].enable_isochronous_mode = 1; pDat->PortList[i+1].enable_isochronous_mode = 1; } } } /*---------------------------------------------------------------------------------------- * void * selectOptimalWidthAndFrequency(sMainData *pDat) * * Description: * For all links: * Examine both sides of a link and determine the optimal frequency and width, * taking into account externally provided limits and enforcing any other limit * or matching rules as applicable except sublink balancing. Update the port * list date with the optimal settings. * Note no hardware state changes in this routine. * * Parameters: * @param[in,out] sMainData* pDat = our global state, port list data * --------------------------------------------------------------------------------------- */ static void selectOptimalWidthAndFrequency(sMainData *pDat) { u8 i, j; uint32_t temp; uint32_t cbPCBFreqLimit; uint32_t cbPCBFreqLimit_NVRAM; u8 cbPCBABDownstreamWidth; u8 cbPCBBAUpstreamWidth; cbPCBFreqLimit_NVRAM = 0xfffff; if (get_option(&temp, "hypertransport_speed_limit") == CB_SUCCESS) cbPCBFreqLimit_NVRAM = ht_speed_limit[temp & 0xf]; if (!is_fam15h()) { /* FIXME * By default limit frequency to 2.6 GHz as there are residual * problems with HT v3.1 implementation on at least some Socket G34 * mainboards / Fam10h CPUs. * Debug the issues and reenable this... */ if (cbPCBFreqLimit_NVRAM > 0xffff) cbPCBFreqLimit_NVRAM = 0xffff; } for (i = 0; i < pDat->TotalLinks*2; i += 2) { cbPCBFreqLimit = 0xfffff; // Maximum allowed by autoconfiguration if (pDat->HtBlock->ht_link_configuration) cbPCBFreqLimit = ht_speed_mhz_to_hw(pDat->HtBlock->ht_link_configuration->ht_speed_limit); cbPCBFreqLimit = min(cbPCBFreqLimit, cbPCBFreqLimit_NVRAM); #if IS_ENABLED(CONFIG_LIMIT_HT_DOWN_WIDTH_8) cbPCBABDownstreamWidth = 8; #else cbPCBABDownstreamWidth = 16; #endif #if IS_ENABLED(CONFIG_LIMIT_HT_UP_WIDTH_8) cbPCBBAUpstreamWidth = 8; #else cbPCBBAUpstreamWidth = 16; #endif if ((pDat->PortList[i].Type == PORTLIST_TYPE_CPU) && (pDat->PortList[i+1].Type == PORTLIST_TYPE_CPU)) { if (pDat->HtBlock->AMD_CB_Cpu2CpuPCBLimits) { pDat->HtBlock->AMD_CB_Cpu2CpuPCBLimits( pDat->PortList[i].NodeID, pDat->PortList[i].Link, pDat->PortList[i+1].NodeID, pDat->PortList[i+1].Link, &cbPCBABDownstreamWidth, &cbPCBBAUpstreamWidth, &cbPCBFreqLimit ); } } else { if (pDat->HtBlock->AMD_CB_IOPCBLimits) { pDat->HtBlock->AMD_CB_IOPCBLimits( pDat->PortList[i+1].NodeID, pDat->PortList[i+1].HostLink, pDat->PortList[i+1].HostDepth, &cbPCBABDownstreamWidth, &cbPCBBAUpstreamWidth, &cbPCBFreqLimit ); } } temp = pDat->PortList[i].PrvFrequencyCap; temp &= pDat->PortList[i+1].PrvFrequencyCap; temp &= cbPCBFreqLimit; pDat->PortList[i].CompositeFrequencyCap = temp; pDat->PortList[i+1].CompositeFrequencyCap = temp; ASSERT (temp != 0); for (j = 19;; j--) { if ((j == 16) || (j == 15)) continue; if (temp & ((uint32_t)1 << j)) break; } pDat->PortList[i].SelFrequency = j; pDat->PortList[i+1].SelFrequency = j; temp = pDat->PortList[i].PrvWidthOutCap; if (pDat->PortList[i+1].PrvWidthInCap < temp) temp = pDat->PortList[i+1].PrvWidthInCap; if (cbPCBABDownstreamWidth < temp) temp = cbPCBABDownstreamWidth; pDat->PortList[i].SelWidthOut = (u8)temp; pDat->PortList[i+1].SelWidthIn = (u8)temp; temp = pDat->PortList[i].PrvWidthInCap; if (pDat->PortList[i+1].PrvWidthOutCap < temp) temp = pDat->PortList[i+1].PrvWidthOutCap; if (cbPCBBAUpstreamWidth < temp) temp = cbPCBBAUpstreamWidth; pDat->PortList[i].SelWidthIn = (u8)temp; pDat->PortList[i+1].SelWidthOut = (u8)temp; } } /*---------------------------------------------------------------------------------------- * void * hammerSublinkFixup(sMainData *pDat) * * Description: * Iterate through all links, checking the frequency of each sublink pair. Make the * adjustment to the port list data so that the frequencies are at a valid ratio, * reducing frequency as needed to achieve this. (All links support the minimum 200 MHz * frequency.) Repeat the above until no adjustments are needed. * Note no hardware state changes in this routine. * * Parameters: * @param[in,out] sMainData* pDat = our global state, link state and port list * --------------------------------------------------------------------------------------- */ static void hammerSublinkFixup(sMainData *pDat) { #ifndef HT_BUILD_NC_ONLY u8 i, j, k; BOOL changes, downgrade; u8 hiIndex; u8 hiFreq, loFreq; u32 temp; do { changes = FALSE; for (i = 0; i < pDat->TotalLinks*2; i++) { if (pDat->PortList[i].Type != PORTLIST_TYPE_CPU) /* Must be a CPU link */ continue; if (pDat->PortList[i].Link < 4) /* Only look for sublink1's */ continue; for (j = 0; j < pDat->TotalLinks*2; j++) { /* Step 1. Find the matching sublink0 */ if (pDat->PortList[j].Type != PORTLIST_TYPE_CPU) continue; if (pDat->PortList[j].NodeID != pDat->PortList[i].NodeID) continue; if (pDat->PortList[j].Link != (pDat->PortList[i].Link & 0x03)) continue; /* Step 2. Check for an illegal frequency ratio */ if (pDat->PortList[i].SelFrequency >= pDat->PortList[j].SelFrequency) { hiIndex = i; hiFreq = pDat->PortList[i].SelFrequency; loFreq = pDat->PortList[j].SelFrequency; } else { hiIndex = j; hiFreq = pDat->PortList[j].SelFrequency; loFreq = pDat->PortList[i].SelFrequency; } if (hiFreq == loFreq) break; /* The frequencies are 1:1, no need to do anything */ downgrade = FALSE; if (hiFreq == 13) { if ((loFreq != 7) && /* {13, 7} 2400MHz / 1200MHz 2:1 */ (loFreq != 4) && /* {13, 4} 2400MHz / 600MHz 4:1 */ (loFreq != 2)) /* {13, 2} 2400MHz / 400MHz 6:1 */ downgrade = TRUE; } else if (hiFreq == 11) { if ((loFreq != 6)) /* {11, 6} 2000MHz / 1000MHz 2:1 */ downgrade = TRUE; } else if (hiFreq == 9) { if ((loFreq != 5) && /* { 9, 5} 1600MHz / 800MHz 2:1 */ (loFreq != 2) && /* { 9, 2} 1600MHz / 400MHz 4:1 */ (loFreq != 0)) /* { 9, 0} 1600MHz / 200MHz 8:1 */ downgrade = TRUE; } else if (hiFreq == 7) { if ((loFreq != 4) && /* { 7, 4} 1200MHz / 600MHz 2:1 */ (loFreq != 0)) /* { 7, 0} 1200MHz / 200MHz 6:1 */ downgrade = TRUE; } else if (hiFreq == 5) { if ((loFreq != 2) && /* { 5, 2} 800MHz / 400MHz 2:1 */ (loFreq != 0)) /* { 5, 0} 800MHz / 200MHz 4:1 */ downgrade = TRUE; } else if (hiFreq == 2) { if ((loFreq != 0)) /* { 2, 0} 400MHz / 200MHz 2:1 */ downgrade = TRUE; } else { downgrade = TRUE; /* no legal ratios for hiFreq */ } /* Step 3. Downgrade the higher of the two frequencies, and set nochanges to FALSE */ if (downgrade) { /* Although the problem was with the port specified by hiIndex, we need to */ /* downgrade both ends of the link. */ hiIndex = hiIndex & 0xFE; /* Select the 'upstream' (i.e. even) port */ temp = pDat->PortList[hiIndex].CompositeFrequencyCap; /* Remove hiFreq from the list of valid frequencies */ temp = temp & ~((uint32)1 << hiFreq); ASSERT (temp != 0); pDat->PortList[hiIndex].CompositeFrequencyCap = temp; pDat->PortList[hiIndex+1].CompositeFrequencyCap = temp; for (k = 19;; k--) { if ((j == 16) || (j == 15)) continue; if (temp & ((uint32_t)1 << k)) break; } pDat->PortList[hiIndex].SelFrequency = k; pDat->PortList[hiIndex+1].SelFrequency = k; changes = TRUE; } } } } while (changes); /* Repeat until a valid configuration is reached */ #endif /* HT_BUILD_NC_ONLY */ } /*---------------------------------------------------------------------------------------- * void * linkOptimization(sMainData *pDat) * * Description: * Based on link capabilities, apply optimization rules to come up with the real best * settings, including several external limit decision from call backs. This includes * handling of sublinks. Finally, after the port list data is updated, set the hardware * state for all links. * * Parameters: * @param[in] sMainData* pDat = our global state * --------------------------------------------------------------------------------------- */ static void linkOptimization(sMainData *pDat) { pDat->nb->gatherLinkData(pDat, pDat->nb); regangLinks(pDat); if (is_fam15h()) detectIoLinkIsochronousCapable(pDat); selectOptimalWidthAndFrequency(pDat); hammerSublinkFixup(pDat); pDat->nb->setLinkData(pDat, pDat->nb); } /*---------------------------------------------------------------------------------------- * void * trafficDistribution(sMainData *pDat) * * Description: * In the case of a two node system with both sublinks used, enable the traffic * distribution feature. * * Parameters: * @param[in] sMainData* pDat = our global state, port list data * --------------------------------------------------------------------------------------- */ static void trafficDistribution(sMainData *pDat) { #ifndef HT_BUILD_NC_ONLY u32 links01, links10; u8 linkCount; u8 i; /* Traffic Distribution is only used when there are exactly two nodes in the system */ if (pDat->NodesDiscovered+1 != 2) return; links01 = 0; links10 = 0; linkCount = 0; for (i = 0; i < pDat->TotalLinks*2; i += 2) { if ((pDat->PortList[i].Type == PORTLIST_TYPE_CPU) && (pDat->PortList[i+1].Type == PORTLIST_TYPE_CPU)) { links01 |= (u32)1 << pDat->PortList[i].Link; links10 |= (u32)1 << pDat->PortList[i+1].Link; linkCount++; } } ASSERT(linkCount != 0); if (linkCount == 1) return; /* Don't setup Traffic Distribution if only one link is being used */ pDat->nb->writeTrafficDistribution(links01, links10, pDat->nb); #endif /* HT_BUILD_NC_ONLY */ } /*---------------------------------------------------------------------------------------- * void * tuning(sMainData *pDat) * * Description: * Handle system and performance tunings, such as traffic distribution, fifo and * buffer tuning, and special config tunings. * * Parameters: * @param[in] sMainData* pDat = our global state, port list data * --------------------------------------------------------------------------------------- */ static void tuning(sMainData *pDat) { u8 i; /* See if traffic distribution can be done and do it if so * or allow system specific customization */ if ((pDat->HtBlock->AMD_CB_CustomizeTrafficDistribution == NULL) || !pDat->HtBlock->AMD_CB_CustomizeTrafficDistribution()) { trafficDistribution(pDat); } /* For each node, invoke northbridge specific buffer tunings or * system specific customizations. */ for (i = 0; i < pDat->NodesDiscovered + 1; i++) { if ((pDat->HtBlock->AMD_CB_CustomizeBuffers == NULL) || !pDat->HtBlock->AMD_CB_CustomizeBuffers(i)) { pDat->nb->bufferOptimizations(i, pDat, pDat->nb); } } } /*---------------------------------------------------------------------------------------- * BOOL * isSanityCheckOk() * * Description: * Perform any general sanity checks which should prevent HT from running if they fail. * Currently only the "Must run on BSP only" check. * * Parameters: * @param[out] result BOOL = true if check is ok, false if it failed * --------------------------------------------------------------------------------------- */ static BOOL isSanityCheckOk(void) { uint64 qValue; AmdMSRRead(LAPIC_BASE_MSR, &qValue); return ((qValue.lo & LAPIC_BASE_MSR_BOOTSTRAP_PROCESSOR) != 0); } /*************************************************************************** *** HT Initialize *** ***************************************************************************/ /*---------------------------------------------------------------------------------------- * void * htInitialize(AMD_HTBLOCK *pBlock) * * Description: * This is the top level external interface for Hypertransport Initialization. * Create our initial internal state, initialize the coherent fabric, * initialize the non-coherent chains, and perform any required fabric tuning or * optimization. * * Parameters: * @param[in] AMD_HTBLOCK* pBlock = Our Initial State including possible * topologies and routings, non coherent bus * assignment info, and actual * wrapper or OEM call back routines. * --------------------------------------------------------------------------------------- */ void amdHtInitialize(AMD_HTBLOCK *pBlock) { sMainData pDat; cNorthBridge nb; if (isSanityCheckOk()) { newNorthBridge(0, &nb); pDat.HtBlock = pBlock; pDat.nb = &nb; pDat.sysMpCap = nb.maxNodes; nb.isCapable(0, &pDat, pDat.nb); coherentInit(&pDat); pDat.AutoBusCurrent = pBlock->AutoBusStart; pDat.UsedCfgMapEntires = 0; ncInit(&pDat); linkOptimization(&pDat); tuning(&pDat); } }