|  | /** | 
|  | * @file IxEthDBEvents.c | 
|  | * | 
|  | * @brief Implementation of the event processor component | 
|  | * | 
|  | * @par | 
|  | * IXP400 SW Release version 2.0 | 
|  | * | 
|  | * -- Copyright Notice -- | 
|  | * | 
|  | * @par | 
|  | * Copyright 2001-2005, Intel Corporation. | 
|  | * All rights reserved. | 
|  | * | 
|  | * @par | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions | 
|  | * are met: | 
|  | * 1. Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | * 2. 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. | 
|  | * 3. Neither the name of the Intel Corporation nor the names of its contributors | 
|  | *    may be used to endorse or promote products derived from this software | 
|  | *    without specific prior written permission. | 
|  | * | 
|  | * @par | 
|  | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' | 
|  | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|  | * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE | 
|  | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
|  | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | 
|  | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | 
|  | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 
|  | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | 
|  | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | 
|  | * SUCH DAMAGE. | 
|  | * | 
|  | * @par | 
|  | * -- End of Copyright Notice -- | 
|  | */ | 
|  |  | 
|  | #include <IxNpeMh.h> | 
|  | #include <IxFeatureCtrl.h> | 
|  |  | 
|  | #include "IxEthDB_p.h" | 
|  |  | 
|  | /* forward prototype declarations */ | 
|  | IX_ETH_DB_PUBLIC void ixEthDBEventProcessorLoop(void *); | 
|  | IX_ETH_DB_PUBLIC void ixEthDBNPEEventCallback(IxNpeMhNpeId npeID, IxNpeMhMessage msg); | 
|  | IX_ETH_DB_PRIVATE void ixEthDBProcessEvent(PortEvent *local_event, IxEthDBPortMap triggerPorts); | 
|  | IX_ETH_DB_PRIVATE IxEthDBStatus ixEthDBTriggerPortUpdate(UINT32 eventType, IxEthDBMacAddr *macAddr, IxEthDBPortId portID, BOOL staticEntry); | 
|  | IX_ETH_DB_PUBLIC IxEthDBStatus ixEthDBStartLearningFunction(void); | 
|  | IX_ETH_DB_PUBLIC IxEthDBStatus ixEthDBStopLearningFunction(void); | 
|  |  | 
|  | /* data */ | 
|  | IX_ETH_DB_PRIVATE IxOsalSemaphore eventQueueSemaphore; | 
|  | IX_ETH_DB_PRIVATE PortEventQueue eventQueue; | 
|  | IX_ETH_DB_PRIVATE IxOsalMutex eventQueueLock; | 
|  | IX_ETH_DB_PRIVATE IxOsalMutex portUpdateLock; | 
|  |  | 
|  | IX_ETH_DB_PRIVATE BOOL ixEthDBLearningShutdown      = FALSE; | 
|  | IX_ETH_DB_PRIVATE BOOL ixEthDBEventProcessorRunning = FALSE; | 
|  |  | 
|  | /* imported data */ | 
|  | extern HashTable dbHashtable; | 
|  |  | 
|  | /** | 
|  | * @brief initializes the event processor | 
|  | * | 
|  | * Initializes the event processor queue and processing thread. | 
|  | * Called from ixEthDBInit() DB-subcomponent master init function. | 
|  | * | 
|  | * @warning do not call directly | 
|  | * | 
|  | * @retval IX_ETH_DB_SUCCESS initialization was successful | 
|  | * @retval IX_ETH_DB_FAIL initialization failed (OSAL or mutex init failure) | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | IxEthDBStatus ixEthDBEventProcessorInit(void) | 
|  | { | 
|  | if (ixOsalMutexInit(&portUpdateLock) != IX_SUCCESS) | 
|  | { | 
|  | return IX_ETH_DB_FAIL; | 
|  | } | 
|  |  | 
|  | if (ixOsalMutexInit(&eventQueueLock) != IX_SUCCESS) | 
|  | { | 
|  | return IX_ETH_DB_FAIL; | 
|  | } | 
|  |  | 
|  | if (IX_FEATURE_CTRL_SWCONFIG_ENABLED == | 
|  | ixFeatureCtrlSwConfigurationCheck (IX_FEATURECTRL_ETH_LEARNING)) | 
|  | { | 
|  |  | 
|  | /* start processor loop thread */ | 
|  | if (ixEthDBStartLearningFunction() != IX_ETH_DB_SUCCESS) | 
|  | { | 
|  | return IX_ETH_DB_FAIL; | 
|  | } | 
|  | } | 
|  |  | 
|  | return IX_ETH_DB_SUCCESS; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief initializes the event queue and the event processor | 
|  | * | 
|  | * This function is called by the component initialization | 
|  | * function, ixEthDBInit(). | 
|  | * | 
|  | * @warning do not call directly | 
|  | * | 
|  | * @return IX_ETH_DB_SUCCESS if the operation completed | 
|  | * successfully or IX_ETH_DB_FAIL otherwise | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | IxEthDBStatus ixEthDBStartLearningFunction(void) | 
|  | { | 
|  | IxOsalThread eventProcessorThread; | 
|  | IxOsalThreadAttr threadAttr; | 
|  |  | 
|  | threadAttr.name      = "EthDB event thread"; | 
|  | threadAttr.stackSize = 32 * 1024; /* 32kbytes */ | 
|  | threadAttr.priority  = 128; | 
|  |  | 
|  | /* reset event queue */ | 
|  | ixOsalMutexLock(&eventQueueLock, IX_OSAL_WAIT_FOREVER); | 
|  |  | 
|  | RESET_QUEUE(&eventQueue); | 
|  |  | 
|  | ixOsalMutexUnlock(&eventQueueLock); | 
|  |  | 
|  | /* init event queue semaphore */ | 
|  | if (ixOsalSemaphoreInit(&eventQueueSemaphore, 0) != IX_SUCCESS) | 
|  | { | 
|  | return IX_ETH_DB_FAIL; | 
|  | } | 
|  |  | 
|  | ixEthDBLearningShutdown = FALSE; | 
|  |  | 
|  | /* create processor loop thread */ | 
|  | if (ixOsalThreadCreate(&eventProcessorThread, &threadAttr, ixEthDBEventProcessorLoop, NULL) != IX_SUCCESS) | 
|  | { | 
|  | return IX_ETH_DB_FAIL; | 
|  | } | 
|  |  | 
|  | /* start event processor */ | 
|  | ixOsalThreadStart(&eventProcessorThread); | 
|  |  | 
|  | return IX_ETH_DB_SUCCESS; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief stops the event processor | 
|  | * | 
|  | * Stops the event processor and frees the event queue semaphore | 
|  | * Called by the component de-initialization function, ixEthDBUnload() | 
|  | * | 
|  | * @warning do not call directly | 
|  | * | 
|  | * @return IX_ETH_DB_SUCCESS if the operation completed | 
|  | * successfully or IX_ETH_DB_FAIL otherwise; | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | IxEthDBStatus ixEthDBStopLearningFunction(void) | 
|  | { | 
|  | ixEthDBLearningShutdown = TRUE; | 
|  |  | 
|  | /* wake up event processing loop to actually process the shutdown event */ | 
|  | ixOsalSemaphorePost(&eventQueueSemaphore); | 
|  |  | 
|  | if (ixOsalSemaphoreDestroy(&eventQueueSemaphore) != IX_SUCCESS) | 
|  | { | 
|  | return IX_ETH_DB_FAIL; | 
|  | } | 
|  |  | 
|  | return IX_ETH_DB_SUCCESS; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief default NPE event processing callback | 
|  | * | 
|  | * @param npeID ID of the NPE that generated the event | 
|  | * @param msg NPE message (encapsulated event) | 
|  | * | 
|  | * Creates an event object on the Ethernet event processor queue | 
|  | * and signals the new event by incrementing the event queue semaphore. | 
|  | * Events are processed by @ref ixEthDBEventProcessorLoop() which runs | 
|  | * at user level. | 
|  | * | 
|  | * @see ixEthDBEventProcessorLoop() | 
|  | * | 
|  | * @warning do not call directly | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | void ixEthDBNPEEventCallback(IxNpeMhNpeId npeID, IxNpeMhMessage msg) | 
|  | { | 
|  | PortEvent *local_event; | 
|  |  | 
|  | IX_ETH_DB_IRQ_EVENTS_TRACE("DB: (Events) new event received by processor callback from port %d, id 0x%X\n", IX_ETH_DB_NPE_TO_PORT_ID(npeID), NPE_MSG_ID(msg), 0, 0, 0, 0); | 
|  |  | 
|  | if (CAN_ENQUEUE(&eventQueue)) | 
|  | { | 
|  | TEST_FIXTURE_LOCK_EVENT_QUEUE; | 
|  |  | 
|  | local_event = QUEUE_HEAD(&eventQueue); | 
|  |  | 
|  | /* create event structure on queue */ | 
|  | local_event->eventType = NPE_MSG_ID(msg); | 
|  | local_event->portID    = IX_ETH_DB_NPE_TO_PORT_ID(npeID); | 
|  |  | 
|  | /* update queue */ | 
|  | PUSH_UPDATE_QUEUE(&eventQueue); | 
|  |  | 
|  | TEST_FIXTURE_UNLOCK_EVENT_QUEUE; | 
|  |  | 
|  | IX_ETH_DB_IRQ_EVENTS_TRACE("DB: (Events) Waking up main processor loop...\n", 0, 0, 0, 0, 0, 0); | 
|  |  | 
|  | /* increment event queue semaphore */ | 
|  | ixOsalSemaphorePost(&eventQueueSemaphore); | 
|  | } | 
|  | else | 
|  | { | 
|  | IX_ETH_DB_IRQ_EVENTS_TRACE("DB: (Events) Warning: could not enqueue event (overflow)\n", 0, 0, 0, 0, 0, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Ethernet event processor loop | 
|  | * | 
|  | * Extracts at most EVENT_PROCESSING_LIMIT batches of events and | 
|  | * sends them for processing to @ref ixEthDBProcessEvent(). | 
|  | * Triggers port updates which normally follow learning events. | 
|  | * | 
|  | * @warning do not call directly, executes in separate thread | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | void ixEthDBEventProcessorLoop(void *unused1) | 
|  | { | 
|  | IxEthDBPortMap triggerPorts; | 
|  | IxEthDBPortId portIndex; | 
|  |  | 
|  | ixEthDBEventProcessorRunning = TRUE; | 
|  |  | 
|  | IX_ETH_DB_EVENTS_TRACE("DB: (Events) Event processor loop was started\n"); | 
|  |  | 
|  | while (!ixEthDBLearningShutdown) | 
|  | { | 
|  | BOOL keepProcessing    = TRUE; | 
|  | UINT32 processedEvents = 0; | 
|  |  | 
|  | IX_ETH_DB_EVENTS_VERBOSE_TRACE("DB: (Events) Waiting for new learning event...\n"); | 
|  |  | 
|  | ixOsalSemaphoreWait(&eventQueueSemaphore, IX_OSAL_WAIT_FOREVER); | 
|  |  | 
|  | IX_ETH_DB_EVENTS_VERBOSE_TRACE("DB: (Events) Received new event\n"); | 
|  |  | 
|  | if (!ixEthDBLearningShutdown) | 
|  | { | 
|  | /* port update handling */ | 
|  | SET_EMPTY_DEPENDENCY_MAP(triggerPorts); | 
|  |  | 
|  | while (keepProcessing) | 
|  | { | 
|  | PortEvent local_event; | 
|  | UINT32 intLockKey; | 
|  |  | 
|  | /* lock queue */ | 
|  | ixOsalMutexLock(&eventQueueLock, IX_OSAL_WAIT_FOREVER); | 
|  |  | 
|  | /* lock NPE interrupts */ | 
|  | intLockKey = ixOsalIrqLock(); | 
|  |  | 
|  | /* extract event */ | 
|  | local_event = *(QUEUE_TAIL(&eventQueue)); | 
|  |  | 
|  | SHIFT_UPDATE_QUEUE(&eventQueue); | 
|  |  | 
|  | ixOsalIrqUnlock(intLockKey); | 
|  |  | 
|  | ixOsalMutexUnlock(&eventQueueLock); | 
|  |  | 
|  | IX_ETH_DB_EVENTS_TRACE("DB: (Events) Processing event with ID 0x%X\n", local_event.eventType); | 
|  |  | 
|  | ixEthDBProcessEvent(&local_event, triggerPorts); | 
|  |  | 
|  | processedEvents++; | 
|  |  | 
|  | if (processedEvents > EVENT_PROCESSING_LIMIT /* maximum burst reached? */ | 
|  | || ixOsalSemaphoreTryWait(&eventQueueSemaphore) != IX_SUCCESS) /* or empty queue? */ | 
|  | { | 
|  | keepProcessing = FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | ixEthDBUpdatePortLearningTrees(triggerPorts); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* turn off automatic updates */ | 
|  | for (portIndex = 0 ; portIndex < IX_ETH_DB_NUMBER_OF_PORTS ; portIndex++) | 
|  | { | 
|  | ixEthDBPortInfo[portIndex].updateMethod.updateEnabled = FALSE; | 
|  | } | 
|  |  | 
|  | ixEthDBEventProcessorRunning = FALSE; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief event processor routine | 
|  | * | 
|  | * @param event event to be processed | 
|  | * @param triggerPorts port map accumulating ports to be updated | 
|  | * | 
|  | * Processes learning events by synchronizing the database with | 
|  | * newly learnt data. Called only by @ref ixEthDBEventProcessorLoop(). | 
|  | * | 
|  | * @warning do not call directly | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PRIVATE | 
|  | void ixEthDBProcessEvent(PortEvent *local_event, IxEthDBPortMap triggerPorts) | 
|  | { | 
|  | MacDescriptor recordTemplate; | 
|  |  | 
|  | switch (local_event->eventType) | 
|  | { | 
|  | case IX_ETH_DB_ADD_FILTERING_RECORD: | 
|  | /* add record */ | 
|  | memset(&recordTemplate, 0, sizeof (recordTemplate)); | 
|  | memcpy(recordTemplate.macAddress, local_event->macAddr.macAddress, sizeof (IxEthDBMacAddr)); | 
|  |  | 
|  | recordTemplate.type   = IX_ETH_DB_FILTERING_RECORD; | 
|  | recordTemplate.portID = local_event->portID; | 
|  | recordTemplate.recordData.filteringData.staticEntry = local_event->staticEntry; | 
|  |  | 
|  | ixEthDBAdd(&recordTemplate, triggerPorts); | 
|  |  | 
|  | IX_ETH_DB_EVENTS_TRACE("DB: (Events) Added record on port %d\n", local_event->portID); | 
|  |  | 
|  | break; | 
|  |  | 
|  | case IX_ETH_DB_REMOVE_FILTERING_RECORD: | 
|  | /* remove record */ | 
|  | memset(&recordTemplate, 0, sizeof (recordTemplate)); | 
|  | memcpy(recordTemplate.macAddress, local_event->macAddr.macAddress, sizeof (IxEthDBMacAddr)); | 
|  |  | 
|  | recordTemplate.type = IX_ETH_DB_FILTERING_RECORD | IX_ETH_DB_FILTERING_VLAN_RECORD; | 
|  |  | 
|  | ixEthDBRemove(&recordTemplate, triggerPorts); | 
|  |  | 
|  | IX_ETH_DB_EVENTS_TRACE("DB: (Events) Removed record on port %d\n", local_event->portID); | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | /* can't handle/not interested in this event type */ | 
|  | ERROR_LOG("DB: (Events) Event processor received an unknown event type (0x%X)\n", local_event->eventType); | 
|  |  | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief asynchronously adds a filtering record | 
|  | * by posting an ADD_FILTERING_RECORD event to the event queue | 
|  | * | 
|  | * @param macAddr MAC address of the new record | 
|  | * @param portID port ID of the new record | 
|  | * @param staticEntry TRUE if record is static, FALSE if dynamic | 
|  | * | 
|  | * @return IX_ETH_DB_SUCCESS if the event creation was | 
|  | * successfull or IX_ETH_DB_BUSY if the event queue is full | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | IxEthDBStatus ixEthDBTriggerAddPortUpdate(IxEthDBMacAddr *macAddr, IxEthDBPortId portID, BOOL staticEntry) | 
|  | { | 
|  | MacDescriptor reference; | 
|  |  | 
|  | TEST_FIXTURE_INCREMENT_DB_CORE_ACCESS_COUNTER; | 
|  |  | 
|  | /* fill search fields */ | 
|  | memcpy(reference.macAddress, macAddr, sizeof (IxEthDBMacAddr)); | 
|  | reference.portID = portID; | 
|  |  | 
|  | /* set acceptable record types */ | 
|  | reference.type = IX_ETH_DB_ALL_FILTERING_RECORDS; | 
|  |  | 
|  | if (ixEthDBPeekHashEntry(&dbHashtable, IX_ETH_DB_MAC_PORT_KEY, &reference) == IX_ETH_DB_SUCCESS) | 
|  | { | 
|  | /* already have an identical record */ | 
|  | return IX_ETH_DB_SUCCESS; | 
|  | } | 
|  | else | 
|  | { | 
|  | return ixEthDBTriggerPortUpdate(IX_ETH_DB_ADD_FILTERING_RECORD, macAddr, portID, staticEntry); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief asynchronously removes a filtering record | 
|  | * by posting a REMOVE_FILTERING_RECORD event to the event queue | 
|  | * | 
|  | * @param macAddr MAC address of the record to remove | 
|  | * @param portID port ID of the record to remove | 
|  | * | 
|  | * @return IX_ETH_DB_SUCCESS if the event creation was | 
|  | * successfull or IX_ETH_DB_BUSY if the event queue is full | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | IxEthDBStatus ixEthDBTriggerRemovePortUpdate(IxEthDBMacAddr *macAddr, IxEthDBPortId portID) | 
|  | { | 
|  | if (ixEthDBPeek(macAddr, IX_ETH_DB_ALL_FILTERING_RECORDS) != IX_ETH_DB_NO_SUCH_ADDR) | 
|  | { | 
|  | return ixEthDBTriggerPortUpdate(IX_ETH_DB_REMOVE_FILTERING_RECORD, macAddr, portID, FALSE); | 
|  | } | 
|  | else | 
|  | { | 
|  | return IX_ETH_DB_NO_SUCH_ADDR; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief adds an ADD or REMOVE event to the main event queue | 
|  | * | 
|  | * @param eventType event type - IX_ETH_DB_ADD_FILTERING_RECORD | 
|  | * to add and IX_ETH_DB_REMOVE_FILTERING_RECORD to remove a | 
|  | * record. | 
|  | * | 
|  | * @return IX_ETH_DB_SUCCESS if the event was successfully | 
|  | * sent or IX_ETH_DB_BUSY if the event queue is full | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PRIVATE | 
|  | IxEthDBStatus ixEthDBTriggerPortUpdate(UINT32 eventType, IxEthDBMacAddr *macAddr, IxEthDBPortId portID, BOOL staticEntry) | 
|  | { | 
|  | UINT32 intLockKey; | 
|  |  | 
|  | /* lock interrupts to protect queue */ | 
|  | intLockKey = ixOsalIrqLock(); | 
|  |  | 
|  | if (CAN_ENQUEUE(&eventQueue)) | 
|  | { | 
|  | PortEvent *queueEvent = QUEUE_HEAD(&eventQueue); | 
|  |  | 
|  | /* update fields on the queue */ | 
|  | memcpy(queueEvent->macAddr.macAddress, macAddr->macAddress, sizeof (IxEthDBMacAddr)); | 
|  |  | 
|  | queueEvent->eventType     = eventType; | 
|  | queueEvent->portID        = portID; | 
|  | queueEvent->staticEntry   = staticEntry; | 
|  |  | 
|  | PUSH_UPDATE_QUEUE(&eventQueue); | 
|  |  | 
|  | /* imcrement event queue semaphore */ | 
|  | ixOsalSemaphorePost(&eventQueueSemaphore); | 
|  |  | 
|  | /* unlock interrupts */ | 
|  | ixOsalIrqUnlock(intLockKey); | 
|  |  | 
|  | return IX_ETH_DB_SUCCESS; | 
|  | } | 
|  | else /* event queue full */ | 
|  | { | 
|  | /* unlock interrupts */ | 
|  | ixOsalIrqUnlock(intLockKey); | 
|  |  | 
|  | return IX_ETH_DB_BUSY; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Locks learning tree updates and port disable | 
|  | * | 
|  | * | 
|  | * This function locks portUpdateLock single mutex. It is primarily used | 
|  | * to avoid executing 'port disable' during ELT maintenance. | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | void ixEthDBUpdateLock(void) | 
|  | { | 
|  | ixOsalMutexLock(&portUpdateLock, IX_OSAL_WAIT_FOREVER); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Unlocks learning tree updates and port disable | 
|  | * | 
|  | * | 
|  | * This function unlocks a portUpdateLock mutex. It is primarily used | 
|  | * to avoid executing 'port disable' during ELT maintenance. | 
|  | * | 
|  | * @internal | 
|  | */ | 
|  | IX_ETH_DB_PUBLIC | 
|  | void ixEthDBUpdateUnlock(void) | 
|  | { | 
|  | ixOsalMutexUnlock(&portUpdateLock); | 
|  | } | 
|  |  |