/** * @file IxEthDBDBPortUpdate.c * * @brief Implementation of dependency and port update handling * * @par * IXP400 SW Release version 2.0 * * -- Copyright Notice -- * * @par * Copyright 2001-2005, Intel Corporation. * All rights reserved. * * @par * SPDX-License-Identifier: BSD-3-Clause * @par * -- End of Copyright Notice -- */ #include "IxEthDB_p.h" /* forward prototype declarations */ IX_ETH_DB_PRIVATE MacTreeNode* ixEthDBTreeInsert(MacTreeNode *searchTree, MacDescriptor *descriptor); IX_ETH_DB_PRIVATE void ixEthDBCreateTrees(IxEthDBPortMap updatePorts); IX_ETH_DB_PRIVATE MacTreeNode* ixEthDBTreeRebalance(MacTreeNode *searchTree); IX_ETH_DB_PRIVATE void ixEthDBRebalanceTreeToVine(MacTreeNode *root, UINT32 *size); IX_ETH_DB_PRIVATE void ixEthDBRebalanceVineToTree(MacTreeNode *root, UINT32 size); IX_ETH_DB_PRIVATE void ixEthDBRebalanceCompression(MacTreeNode *root, UINT32 count); IX_ETH_DB_PRIVATE UINT32 ixEthDBRebalanceLog2Floor(UINT32 x); extern HashTable dbHashtable; /** * @brief register types requiring automatic updates * * @param typeArray array indexed on record types, each * element indicating whether the record type requires an * automatic update (true) or not (false) * * Automatic updates are done for registered record types * upon adding, updating (that is, updating the record portID) * and removing records. Whenever an automatic update is triggered * the appropriate ports will be provided with new database * information. * * It is assumed that the typeArray parameter is allocated large * enough to hold all the user defined types. Also, the type * array should be initialized to false as this function only * caters for types which do require automatic updates. * * Note that this function should be called by the component * initialization function. * * @return number of record types registered for automatic * updates * * @internal */ IX_ETH_DB_PUBLIC UINT32 ixEthDBUpdateTypeRegister(BOOL *typeArray) { typeArray[IX_ETH_DB_FILTERING_RECORD] = true; typeArray[IX_ETH_DB_FILTERING_VLAN_RECORD] = true; return 2; } /** * @brief computes dependencies and triggers port learning tree updates * * @param triggerPorts port map consisting in the ports which triggered the update * * This function browses through all the ports and determines how to waterfall the update * event from the trigger ports to all other ports depending on them. * * Once the list of ports to be updated is determined this function * calls @ref ixEthDBCreateTrees. * * @internal */ IX_ETH_DB_PUBLIC void ixEthDBUpdatePortLearningTrees(IxEthDBPortMap triggerPorts) { IxEthDBPortMap updatePorts; UINT32 portIndex; ixEthDBUpdateLock(); SET_EMPTY_DEPENDENCY_MAP(updatePorts); for (portIndex = 0 ; portIndex < IX_ETH_DB_NUMBER_OF_PORTS ; portIndex++) { PortInfo *port = &ixEthDBPortInfo[portIndex]; BOOL mapsCollide; MAPS_COLLIDE(mapsCollide, triggerPorts, port->dependencyPortMap); if (mapsCollide /* do triggers influence this port? */ && !IS_PORT_INCLUDED(portIndex, updatePorts) /* and it's not already in the update list */ && port->updateMethod.updateEnabled) /* and we're allowed to update it */ { IX_ETH_DB_UPDATE_TRACE("DB: (Update) Adding port %d to update set\n", portIndex); JOIN_PORT_TO_MAP(updatePorts, portIndex); } else { IX_ETH_DB_UPDATE_TRACE("DB: (Update) Didn't add port %d to update set, reasons follow:\n", portIndex); if (!mapsCollide) { IX_ETH_DB_UPDATE_TRACE("\tMaps don't collide on port %d\n", portIndex); } if (IS_PORT_INCLUDED(portIndex, updatePorts)) { IX_ETH_DB_UPDATE_TRACE("\tPort %d is already in the update set\n", portIndex); } if (!port->updateMethod.updateEnabled) { IX_ETH_DB_UPDATE_TRACE("\tPort %d doesn't have updateEnabled set\n", portIndex); } } } IX_ETH_DB_UPDATE_TRACE("DB: (Update) Updating port set\n"); ixEthDBCreateTrees(updatePorts); ixEthDBUpdateUnlock(); } /** * @brief creates learning trees and calls the port update handlers * * @param updatePorts set of ports in need of learning trees * * This function determines the optimal method of creating learning * trees using a minimal number of database queries, keeping in mind * that different ports can either use the same learning trees or they * can partially share them. The actual tree building routine is * @ref ixEthDBQuery. * * @internal */ IX_ETH_DB_PRIVATE void ixEthDBCreateTrees(IxEthDBPortMap updatePorts) { UINT32 portIndex; BOOL result; BOOL portsLeft = true; while (portsLeft) { /* get port with minimal dependency map and NULL search tree */ UINT32 minPortIndex = MAX_PORT_SIZE; UINT32 minimalSize = MAX_PORT_SIZE; for (portIndex = 0 ; portIndex < IX_ETH_DB_NUMBER_OF_PORTS ; portIndex++) { UINT32 size; PortInfo *port = &ixEthDBPortInfo[portIndex]; /* generate trees only for ports that need them */ if (!port->updateMethod.searchTreePendingWrite && IS_PORT_INCLUDED(portIndex, updatePorts)) { GET_MAP_SIZE(port->dependencyPortMap, size); IX_ETH_DB_UPDATE_TRACE("DB: (Update) Dependency map for port %d: size %d\n", portIndex, size); if (size < minimalSize) { minPortIndex = portIndex; minimalSize = size; } } else { IX_ETH_DB_UPDATE_TRACE("DB: (Update) Skipped port %d from tree diff (%s)\n", portIndex, port->updateMethod.searchTreePendingWrite ? "pending write access" : "ignored by query"); } } /* if a port was found than minimalSize is not MAX_PORT_SIZE */ if (minimalSize != MAX_PORT_SIZE) { /* minPortIndex is the port we seek */ PortInfo *port = &ixEthDBPortInfo[minPortIndex]; IxEthDBPortMap query; MacTreeNode *baseTree; /* now try to find a port with minimal map difference */ PortInfo *minimalDiffPort = NULL; UINT32 minimalDiff = MAX_PORT_SIZE; IX_ETH_DB_UPDATE_TRACE("DB: (Update) Minimal size port is %d\n", minPortIndex); for (portIndex = 0 ; portIndex < IX_ETH_DB_NUMBER_OF_PORTS ; portIndex++) { PortInfo *diffPort = &ixEthDBPortInfo[portIndex]; BOOL mapIsSubset; IS_MAP_SUBSET(mapIsSubset, diffPort->dependencyPortMap, port->dependencyPortMap); if (portIndex != minPortIndex && diffPort->updateMethod.searchTree != NULL && mapIsSubset) { /* compute size and pick only minimal size difference */ UINT32 diffPortSize; UINT32 sizeDifference; GET_MAP_SIZE(diffPort->dependencyPortMap, diffPortSize); IX_ETH_DB_UPDATE_TRACE("DB: (Update) Checking port %d for differences...\n", portIndex); sizeDifference = minimalSize - diffPortSize; if (sizeDifference < minimalDiff) { minimalDiffPort = diffPort; minimalDiff = sizeDifference; IX_ETH_DB_UPDATE_TRACE("DB: (Update) Minimal difference 0x%x was found on port %d\n", minimalDiff, portIndex); } } } /* check if filtering is enabled on this port */ if ((port->featureStatus & IX_ETH_DB_FILTERING) != 0) { /* if minimalDiff is not MAX_PORT_SIZE minimalDiffPort points to the most similar port */ if (minimalDiff != MAX_PORT_SIZE) { baseTree = ixEthDBCloneMacTreeNode(minimalDiffPort->updateMethod.searchTree); DIFF_MAPS(query, port->dependencyPortMap , minimalDiffPort->dependencyPortMap); IX_ETH_DB_UPDATE_TRACE("DB: (Update) Found minimal diff, extending tree %d on query\n", minimalDiffPort->portID); } else /* .. otherwise no similar port was found, build tree from scratch */ { baseTree = NULL; COPY_DEPENDENCY_MAP(query, port->dependencyPortMap); IX_ETH_DB_UPDATE_TRACE("DB: (Update) No similar diff, creating tree from query\n"); } IS_EMPTY_DEPENDENCY_MAP(result, query); if (!result) /* otherwise we don't need anything more on top of the cloned tree */ { IX_ETH_DB_UPDATE_TRACE("DB: (Update) Adding query tree to port %d\n", minPortIndex); /* build learning tree */ port->updateMethod.searchTree = ixEthDBQuery(baseTree, query, IX_ETH_DB_ALL_FILTERING_RECORDS, MAX_ELT_SIZE); } else { IX_ETH_DB_UPDATE_TRACE("DB: (Update) Query is empty, assuming identical nearest tree\n"); port->updateMethod.searchTree = baseTree; } } else { /* filtering is not enabled, will download an empty tree */ if (port->updateMethod.searchTree != NULL) { ixEthDBFreeMacTreeNode(port->updateMethod.searchTree); } port->updateMethod.searchTree = NULL; } /* mark tree as valid */ port->updateMethod.searchTreePendingWrite = true; } else { portsLeft = false; IX_ETH_DB_UPDATE_TRACE("DB: (Update) No trees to create this round\n"); } } for (portIndex = 0 ; portIndex < IX_ETH_DB_NUMBER_OF_PORTS ; portIndex++) { PortInfo *updatePort = &ixEthDBPortInfo[portIndex]; if (updatePort->updateMethod.searchTreePendingWrite) { IX_ETH_DB_UPDATE_TRACE("DB: (PortUpdate) Starting procedure to upload new search tree (%snull) into NPE %d\n", updatePort->updateMethod.searchTree != NULL ? "not " : "", portIndex); updatePort->updateMethod.updateHandler(portIndex, IX_ETH_DB_FILTERING_RECORD); } } } /** * @brief standard NPE update handler * * @param portID id of the port to be updated * @param type record type to be pushed during this update * * The NPE update handler manages updating the NPE databases * given a certain record type. * * @internal */ IX_ETH_DB_PUBLIC IxEthDBStatus ixEthDBNPEUpdateHandler(IxEthDBPortId portID, IxEthDBRecordType type) { UINT32 epDelta, blockCount; IxNpeMhMessage message; UINT32 treeSize = 0; PortInfo *port = &ixEthDBPortInfo[portID]; /* size selection and type check */ if (type == IX_ETH_DB_FILTERING_RECORD || type == IX_ETH_DB_WIFI_RECORD) { treeSize = FULL_ELT_BYTE_SIZE; } else if (type == IX_ETH_DB_FIREWALL_RECORD) { treeSize = FULL_FW_BYTE_SIZE; } else { return IX_ETH_DB_INVALID_ARG; } /* serialize tree into memory */ ixEthDBNPETreeWrite(type, treeSize, port->updateMethod.npeUpdateZone, port->updateMethod.searchTree, &epDelta, &blockCount); /* free internal copy */ if (port->updateMethod.searchTree != NULL) { ixEthDBFreeMacTreeNode(port->updateMethod.searchTree); } /* forget last used search tree */ port->updateMethod.searchTree = NULL; port->updateMethod.searchTreePendingWrite = false; /* dependending on the update type we do different things */ if (type == IX_ETH_DB_FILTERING_RECORD || type == IX_ETH_DB_WIFI_RECORD) { IX_STATUS result; FILL_SETMACADDRESSDATABASE_MSG(message, IX_ETH_DB_PORT_ID_TO_NPE_LOGICAL_ID(portID), epDelta, blockCount, IX_OSAL_MMU_VIRT_TO_PHYS(port->updateMethod.npeUpdateZone)); IX_ETHDB_SEND_NPE_MSG(IX_ETH_DB_PORT_ID_TO_NPE(portID), message, result); if (result == IX_SUCCESS) { IX_ETH_DB_UPDATE_TRACE("DB: (PortUpdate) Finished downloading NPE tree on port %d\n", portID); } else { ixEthDBPortInfo[portID].agingEnabled = false; ixEthDBPortInfo[portID].updateMethod.updateEnabled = false; ixEthDBPortInfo[portID].updateMethod.userControlled = true; ERROR_LOG("EthDB: (PortUpdate) disabling aging and updates on port %d (assumed dead)\n", portID); ixEthDBDatabaseClear(portID, IX_ETH_DB_ALL_RECORD_TYPES); return IX_ETH_DB_FAIL; } return IX_ETH_DB_SUCCESS; } else if (type == IX_ETH_DB_FIREWALL_RECORD) { return ixEthDBFirewallUpdate(portID, port->updateMethod.npeUpdateZone, epDelta); } return IX_ETH_DB_INVALID_ARG; } /** * @brief queries the database for a set of records to be inserted into a given tree * * @param searchTree pointer to a tree where insertions will be performed; can be NULL * @param query set of ports that a database record must match to be inserted into the tree * * The query method browses through the database, extracts all the descriptors matching * the given query parameter and inserts them into the given learning tree. * Note that this is an append procedure, the given tree needs not to be empty. * A "descriptor matching the query" is a descriptor whose port id is in the query map. * If the given tree is empty (NULL) a new tree is created and returned. * * @return the tree root * * @internal */ IX_ETH_DB_PUBLIC MacTreeNode* ixEthDBQuery(MacTreeNode *searchTree, IxEthDBPortMap query, IxEthDBRecordType recordFilter, UINT32 maxEntries) { HashIterator iterator; UINT32 entryCount = 0; /* browse database */ BUSY_RETRY(ixEthDBInitHashIterator(&dbHashtable, &iterator)); while (IS_ITERATOR_VALID(&iterator)) { MacDescriptor *descriptor = (MacDescriptor *) iterator.node->data; IX_ETH_DB_UPDATE_TRACE("DB: (PortUpdate) querying [%s]:%d on port map ... ", mac2string(descriptor->macAddress), descriptor->portID); if ((descriptor->type & recordFilter) != 0 && IS_PORT_INCLUDED(descriptor->portID, query)) { MacDescriptor *descriptorClone = ixEthDBCloneMacDescriptor(descriptor); IX_ETH_DB_UPDATE_TRACE("match\n"); if (descriptorClone != NULL) { /* add descriptor to tree */ searchTree = ixEthDBTreeInsert(searchTree, descriptorClone); entryCount++; } } else { IX_ETH_DB_UPDATE_TRACE("no match\n"); } if (entryCount < maxEntries) { /* advance to the next record */ BUSY_RETRY(ixEthDBIncrementHashIterator(&dbHashtable, &iterator)); } else { /* the NPE won't accept more entries so we can stop now */ ixEthDBReleaseHashIterator(&iterator); IX_ETH_DB_UPDATE_TRACE("DB: (PortUpdate) number of elements reached maximum supported by port\n"); break; } } IX_ETH_DB_UPDATE_TRACE("DB: (PortUpdate) query inserted %d records in the search tree\n", entryCount); return ixEthDBTreeRebalance(searchTree); } /** * @brief inserts a mac descriptor into an tree * * @param searchTree tree where the insertion is to be performed (may be NULL) * @param descriptor descriptor to insert into tree * * @return the tree root * * @internal */ IX_ETH_DB_PRIVATE MacTreeNode* ixEthDBTreeInsert(MacTreeNode *searchTree, MacDescriptor *descriptor) { MacTreeNode *currentNode = searchTree; MacTreeNode *insertLocation = NULL; MacTreeNode *newNode; INT32 insertPosition = RIGHT; if (descriptor == NULL) { return searchTree; } /* create a new node */ newNode = ixEthDBAllocMacTreeNode(); if (newNode == NULL) { /* out of memory */ ERROR_LOG("Warning: ixEthDBAllocMacTreeNode returned NULL in file %s:%d (out of memory?)\n", __FILE__, __LINE__); ixEthDBFreeMacDescriptor(descriptor); return NULL; } /* populate node */ newNode->descriptor = descriptor; /* an empty initial tree is a special case */ if (searchTree == NULL) { return newNode; } /* get insertion location */ while (insertLocation == NULL) { MacTreeNode *nextNode; /* compare given key with current node key */ insertPosition = ixEthDBAddressCompare(descriptor->macAddress, currentNode->descriptor->macAddress); /* navigate down */ if (insertPosition == RIGHT) { nextNode = currentNode->right; } else if (insertPosition == LEFT) { nextNode = currentNode->left; } else { /* error, duplicate key */ ERROR_LOG("Warning: trapped insertion of a duplicate MAC address in an NPE search tree\n"); /* this will free the MAC descriptor as well */ ixEthDBFreeMacTreeNode(newNode); return searchTree; } /* when we can no longer dive through the tree we found the insertion place */ if (nextNode != NULL) { currentNode = nextNode; } else { insertLocation = currentNode; } } /* insert node */ if (insertPosition == RIGHT) { insertLocation->right = newNode; } else { insertLocation->left = newNode; } return searchTree; } /** * @brief balance a tree * * @param searchTree tree to balance * * Converts a tree into a balanced tree and returns the root of * the balanced tree. The resulting tree is route balanced * not perfectly balanced. This makes no difference to the * average tree search time which is the same in both cases, O(log2(n)). * * @return root of the balanced tree or NULL if there's no memory left * * @internal */ IX_ETH_DB_PRIVATE MacTreeNode* ixEthDBTreeRebalance(MacTreeNode *searchTree) { MacTreeNode *pseudoRoot = ixEthDBAllocMacTreeNode(); UINT32 size; if (pseudoRoot == NULL) { /* out of memory */ return NULL; } pseudoRoot->right = searchTree; ixEthDBRebalanceTreeToVine(pseudoRoot, &size); ixEthDBRebalanceVineToTree(pseudoRoot, size); searchTree = pseudoRoot->right; /* remove pseudoRoot right branch, otherwise it will free the entire tree */ pseudoRoot->right = NULL; ixEthDBFreeMacTreeNode(pseudoRoot); return searchTree; } /** * @brief converts a tree into a vine * * @param root root of tree to convert * @param size depth of vine (equal to the number of nodes in the tree) * * @internal */ IX_ETH_DB_PRIVATE void ixEthDBRebalanceTreeToVine(MacTreeNode *root, UINT32 *size) { MacTreeNode *vineTail = root; MacTreeNode *remainder = vineTail->right; MacTreeNode *tempPtr; *size = 0; while (remainder != NULL) { if (remainder->left == NULL) { /* move tail down one */ vineTail = remainder; remainder = remainder->right; (*size)++; } else { /* rotate around remainder */ tempPtr = remainder->left; remainder->left = tempPtr->right; tempPtr->right = remainder; remainder = tempPtr; vineTail->right = tempPtr; } } } /** * @brief converts a vine into a balanced tree * * @param root vine to convert * @param size depth of vine * * @internal */ IX_ETH_DB_PRIVATE void ixEthDBRebalanceVineToTree(MacTreeNode *root, UINT32 size) { UINT32 leafCount = size + 1 - (1 << ixEthDBRebalanceLog2Floor(size + 1)); ixEthDBRebalanceCompression(root, leafCount); size = size - leafCount; while (size > 1) { ixEthDBRebalanceCompression(root, size / 2); size /= 2; } } /** * @brief compresses a vine/tree stage into a more balanced vine/tree * * @param root root of the tree to compress * @param count number of "spine" nodes * * @internal */ IX_ETH_DB_PRIVATE void ixEthDBRebalanceCompression(MacTreeNode *root, UINT32 count) { MacTreeNode *scanner = root; MacTreeNode *child; UINT32 local_index; for (local_index = 0 ; local_index < count ; local_index++) { child = scanner->right; scanner->right = child->right; scanner = scanner->right; child->right = scanner->left; scanner->left = child; } } /** * @brief computes |_log2(x)_| (a.k.a. floor(log2(x))) * * @param x number to compute |_log2(x)_| for * * @return |_log2(x)_| * * @internal */ IX_ETH_DB_PRIVATE UINT32 ixEthDBRebalanceLog2Floor(UINT32 x) { UINT32 log = 0; UINT32 val = 1; while (val < x) { log++; val <<= 1; } return val == x ? log : log - 1; }