Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

888 lines
25 KiB

  1. /*++
  2. Copyright(c) 1999-2000 Microsoft Corporation
  3. Module Name:
  4. brdghash.c
  5. Abstract:
  6. Ethernet MAC level bridge.
  7. Hash Table section
  8. This module implements a flexible hash table with support
  9. for timing out entries automatically
  10. Author:
  11. Mark Aiken
  12. Environment:
  13. Kernel mode driver
  14. Revision History:
  15. October 2000 - Original version
  16. --*/
  17. #define NDIS_MINIPORT_DRIVER
  18. #define NDIS50_MINIPORT 1
  19. #define NDIS_WDM 1
  20. #pragma warning( push, 3 )
  21. #include <ndis.h>
  22. #include <ntddk.h>
  23. #pragma warning( pop )
  24. #include "bridge.h"
  25. // ===========================================================================
  26. //
  27. // PRIVATE PROTOTYPES
  28. //
  29. // ===========================================================================
  30. VOID
  31. BrdgHashTimer(
  32. IN PVOID DeferredContext
  33. );
  34. PHASH_TABLE_ENTRY
  35. BrdgHashInternalFindEntry(
  36. IN PHASH_TABLE pTable,
  37. IN PUCHAR pKey
  38. );
  39. // ===========================================================================
  40. //
  41. // GLOBALS
  42. //
  43. // ===========================================================================
  44. // Interval at which the timer runs to clean out table entries
  45. #define TIMER_INTERVAL (10 * 1000) // 10 seconds in milliseconds
  46. // Maximum number of table entries the timer should look at each time through
  47. #define MAX_TIMER_EXAMINES 1000
  48. // ===========================================================================
  49. //
  50. // INLINES
  51. //
  52. // ===========================================================================
  53. //
  54. // Returns TRUE if the two keys of the given length are equal.
  55. //
  56. __forceinline
  57. BOOLEAN
  58. BrdgHashKeysAreEqual(
  59. IN PUCHAR pKeyA,
  60. IN PUCHAR pKeyB,
  61. IN UINT keyLen
  62. )
  63. {
  64. BOOLEAN bEqual = TRUE;
  65. UINT i;
  66. for( i = 0; i < keyLen; i++ )
  67. {
  68. if( pKeyA[i] != pKeyB[i] )
  69. {
  70. bEqual = FALSE;
  71. break;
  72. }
  73. }
  74. return bEqual;
  75. }
  76. //
  77. // Copies the data at pSrcKey to pDestKey
  78. //
  79. __forceinline
  80. VOID
  81. BrdgHashCopyKey(
  82. IN PUCHAR pDestKey,
  83. IN PUCHAR pSrcKey,
  84. IN UINT keyLen
  85. )
  86. {
  87. UINT i;
  88. for( i = 0; i < keyLen; i++ )
  89. {
  90. pDestKey[i] = pSrcKey[i];
  91. }
  92. }
  93. // ===========================================================================
  94. //
  95. // PUBLIC FUNCTIONS
  96. //
  97. // ===========================================================================
  98. PHASH_TABLE
  99. BrdgHashCreateTable(
  100. IN PHASH_FUNCTION pHashFunction,
  101. IN ULONG numBuckets,
  102. IN ULONG entrySize,
  103. IN ULONG maxEntries,
  104. IN ULONG startTimeoutAge,
  105. IN ULONG maxTimeoutAge,
  106. IN UINT keySize
  107. )
  108. /*++
  109. Routine Description:
  110. Initializes a hash table.
  111. Arguments:
  112. pHashFunction The function that can hash a key to a bucket number
  113. numBuckets The number of hash buckets to use
  114. entrySize The total size of each bucket entry (must be at
  115. least sizeof(HASH_TABLE_ENTRY) )
  116. maxEntries A maximum number of entries to enforce
  117. startTimeoutAge The starting timeout value for table entries
  118. (can be changed later)
  119. maxTimeoutAge The highest value the timeout age will ever be
  120. (for sanity checking timestamp delta
  121. calculations)
  122. keySize The size of key to use
  123. Return Value:
  124. The new hash table or NULL if a memory allocation failed
  125. --*/
  126. {
  127. NDIS_STATUS Status;
  128. PHASH_TABLE pTable;
  129. ULONG i;
  130. SAFEASSERT( pHashFunction != NULL );
  131. SAFEASSERT( keySize <= MAX_SUPPORTED_KEYSIZE );
  132. SAFEASSERT( entrySize >= sizeof(HASH_TABLE_ENTRY) );
  133. // Allocate memory for the table info
  134. Status = NdisAllocateMemoryWithTag( &pTable, sizeof(HASH_TABLE), 'gdrB' );
  135. if( Status != NDIS_STATUS_SUCCESS )
  136. {
  137. return NULL;
  138. }
  139. SAFEASSERT( pTable != NULL );
  140. // Allocate memory for the list of bucket heads
  141. Status = NdisAllocateMemoryWithTag( (PVOID*)&pTable->pBuckets, sizeof(PHASH_TABLE_ENTRY) * numBuckets, 'gdrB' );
  142. if( Status != NDIS_STATUS_SUCCESS )
  143. {
  144. NdisFreeMemory( pTable, sizeof(HASH_TABLE), 0 );
  145. return NULL;
  146. }
  147. SAFEASSERT( pTable->pBuckets != NULL );
  148. // Zero out the bucket heads
  149. for( i = 0L; i < numBuckets; i++ )
  150. {
  151. pTable->pBuckets[i] = NULL;
  152. }
  153. #if DBG
  154. // Allocate memory for the list where we keep track of the number
  155. // of items currently in each bucket (debug only)
  156. Status = NdisAllocateMemoryWithTag( &pTable->bucketSizes, sizeof(UINT) * numBuckets, 'gdrB' );
  157. if( Status != NDIS_STATUS_SUCCESS )
  158. {
  159. NdisFreeMemory( pTable->pBuckets, sizeof(PHASH_TABLE_ENTRY) * numBuckets, 0 );
  160. NdisFreeMemory( pTable, sizeof(HASH_TABLE), 0 );
  161. return NULL;
  162. }
  163. SAFEASSERT( pTable->bucketSizes != NULL );
  164. // Zero out the bucket counts
  165. for( i = 0L; i < numBuckets; i++ )
  166. {
  167. pTable->bucketSizes[i] = 0;
  168. }
  169. #endif
  170. pTable->pHashFunction = pHashFunction;
  171. pTable->entrySize = entrySize;
  172. pTable->numBuckets = numBuckets;
  173. pTable->maxEntries = maxEntries;
  174. pTable->numEntries = 0L;
  175. pTable->nextTimerBucket = 0L;
  176. pTable->keySize = keySize;
  177. pTable->timeoutAge = startTimeoutAge;
  178. pTable->maxTimeoutAge = maxTimeoutAge;
  179. NdisInitializeReadWriteLock( &pTable->tableLock );
  180. // Initialize the lookaside list for allocating entries
  181. NdisInitializeNPagedLookasideList( &pTable->entryPool, NULL, NULL, 0, entrySize, 'hsaH', 0 );
  182. // Initialize and start the timer
  183. SAFEASSERT( pTable->timeoutAge != 0L );
  184. SAFEASSERT( pTable->maxTimeoutAge >= pTable->timeoutAge );
  185. BrdgInitializeTimer( &pTable->timer, BrdgHashTimer, pTable );
  186. BrdgSetTimer( &pTable->timer, TIMER_INTERVAL, TRUE /*Recurring*/ );
  187. return pTable;
  188. }
  189. VOID
  190. BrdgHashFreeHashTable(
  191. IN PHASH_TABLE pTable
  192. )
  193. /*++
  194. Routine Description:
  195. Frees an existing hash table structure. Must be called at
  196. low IRQL. Caller is responsible for ensuring that no other
  197. thread can access the table after this function is called.
  198. Arguments:
  199. pTable The table to free
  200. Return Value:
  201. None
  202. --*/
  203. {
  204. // Cancel the timer
  205. BrdgShutdownTimer( &pTable->timer );
  206. // Dump all memory for the hash table entries
  207. NdisDeleteNPagedLookasideList( &pTable->entryPool );
  208. // Dump the memory used for the bucket heads
  209. NdisFreeMemory( pTable->pBuckets, sizeof(PHASH_TABLE_ENTRY) * pTable->numBuckets, 0 );
  210. pTable->pBuckets = NULL;
  211. #if DBG
  212. // Dump the memory used to track the number of entries in each bucket
  213. NdisFreeMemory( pTable->bucketSizes, sizeof(UINT) * pTable->numBuckets, 0 );
  214. pTable->bucketSizes = NULL;
  215. #endif
  216. // Dump the memory for the table itself
  217. NdisFreeMemory( pTable, sizeof(HASH_TABLE), 0 );
  218. }
  219. PHASH_TABLE_ENTRY
  220. BrdgHashFindEntry(
  221. IN PHASH_TABLE pTable,
  222. IN PUCHAR pKey,
  223. IN LOCK_STATE *pLockState
  224. )
  225. /*++
  226. Routine Description:
  227. Finds the table entry with the given key.
  228. If this function returns with a non-NULL result, THE TABLE LOCK IS STILL HELD!
  229. This allows the table entry to be examined without the risk of it being removed
  230. from the table. The caller can copy out any data it is interested in before
  231. releasing the RW lock
  232. Arguments:
  233. pTable The table to search in
  234. pKey The key to find
  235. pLockState Receives the table lock state
  236. Return Value:
  237. The entry whose key matches pKey or NULL if no entry matches
  238. --*/
  239. {
  240. PHASH_TABLE_ENTRY pEntry;
  241. NdisAcquireReadWriteLock( &pTable->tableLock, FALSE /*Read only*/, pLockState);
  242. pEntry = BrdgHashInternalFindEntry(pTable, pKey);
  243. if( pEntry != NULL )
  244. {
  245. ULONG LastSeen = pEntry->LastSeen;
  246. ULONG CurrentTime;
  247. // Always get the current time after having read LastSeen so we know that
  248. // CurrentTime > LastSeen.
  249. NdisGetSystemUpTime( &CurrentTime );
  250. // Check to make sure the entry hasn't expired before using it
  251. // This can happen if our timer function hasn't gotten around to removing
  252. // this entry yet
  253. //
  254. // There is no sensible maximum removal time for hash table entries
  255. if( BrdgDeltaSafe(LastSeen, CurrentTime, MAXULONG) >= pTable->timeoutAge )
  256. {
  257. // We're going to return NULL, so release the table lock
  258. NdisReleaseReadWriteLock( &pTable->tableLock, pLockState );
  259. pEntry = NULL;
  260. }
  261. else
  262. {
  263. // RETURN WITHOUT RELEASING LOCK!
  264. }
  265. }
  266. else
  267. {
  268. NdisReleaseReadWriteLock( &pTable->tableLock, pLockState );
  269. }
  270. return pEntry;
  271. }
  272. PHASH_TABLE_ENTRY
  273. BrdgHashRefreshOrInsert(
  274. IN PHASH_TABLE pTable,
  275. IN PUCHAR pKey,
  276. OUT BOOLEAN *pIsNewEntry,
  277. OUT PLOCK_STATE pLockState
  278. )
  279. /*++
  280. Routine Description:
  281. Inserts a new entry with the given key or refreshes an existing entry
  282. that already has that key.
  283. Care is taken to avoid taking a write lock (and blocking other procs
  284. from accessing the table) if at all possible.
  285. The return value is the entry corresponding to the key, or the new
  286. entry that has been linked into the table; the pIsNewEntry value
  287. distinguishes the cases.
  288. THE FUNCTION RETURNS WITH THE TABLE LOCK IS HELD IF THE RETURNED
  289. VALUE IS != NULL.
  290. A NULL return value indicates that the table is full or an error
  291. occured allocating a new entry. The lock is not held in such a case.
  292. If the return value is not NULL:
  293. If *pIsNewEntry is FALSE, the returned value is an existing entry.
  294. A READ LOCK may be held (under certain circumstances a write lock
  295. is held, but the caller should assume the weaker lock). The
  296. caller may take the opportunity to refresh data in the existing
  297. entry, but he should take care to allow for synchronization of the
  298. data, as other threads may be reading the data.
  299. If *pIsNewEntry is TRUE, the returned value is a new entry, and
  300. a WRITE LOCK is held. The caller may initialize the new table entry
  301. in any way he wishes without worrying about other threads reading
  302. the entry.
  303. THE CALLER IS REPONSIBLE FOR FREEING THE TABLE LOCK IF THE RETURN
  304. VALUE IS != NULL!
  305. Arguments:
  306. pTable The table
  307. pKey The key
  308. pIsNewEntry TRUE if the returned entry is a newly
  309. allocated entry needing initialization
  310. FALSE if the returned entry is an existing
  311. entry
  312. pLockState Receives the state of the table lock
  313. Return Value:
  314. The existing entry (so the caller can refresh it) or the new entry
  315. (so the caller can initialize it), or NULL, signalling that the
  316. table is full or an error occurred.
  317. --*/
  318. {
  319. PHASH_TABLE_ENTRY pRetVal = NULL;
  320. ULONG hash;
  321. ULONG CurrentTime;
  322. SAFEASSERT( pIsNewEntry != NULL );
  323. SAFEASSERT( pLockState != NULL );
  324. NdisGetSystemUpTime( &CurrentTime );
  325. // First see if an entry already exists that we can tweak without taking a write lock
  326. NdisAcquireReadWriteLock( &pTable->tableLock, FALSE /*Read only*/, pLockState);
  327. pRetVal = BrdgHashInternalFindEntry(pTable, pKey);
  328. if( pRetVal != NULL )
  329. {
  330. // It was already recorded. Update the LastSeen with interlocked instructions.
  331. InterlockedExchangeULong( &pRetVal->LastSeen, CurrentTime );
  332. // Return without releasing the lock to let the caller refresh the entry
  333. *pIsNewEntry = FALSE;
  334. }
  335. else
  336. {
  337. // Sanity
  338. SAFEASSERT( pTable->numEntries <= pTable->maxEntries );
  339. if( pTable->numEntries == pTable->maxEntries )
  340. {
  341. // The table is full. Don't put anything more in.
  342. THROTTLED_DBGPRINT(GENERAL, ("Table %p full at %i entries!\n", pTable, pTable->maxEntries));
  343. // Release the lock; we will return NULL.
  344. NdisReleaseReadWriteLock(&pTable->tableLock, pLockState);
  345. }
  346. else
  347. {
  348. // We will need a write lock to link in a new entry, so release the read lock.
  349. NdisReleaseReadWriteLock(&pTable->tableLock, pLockState);
  350. // Allocate the new table entry outside a lock for perf. Note that it's possible
  351. // we'll have to dealloc this without using it below.
  352. pRetVal = NdisAllocateFromNPagedLookasideList( &pTable->entryPool );
  353. if( pRetVal == NULL )
  354. {
  355. DBGPRINT(GENERAL, ("Allocation failed in BrdgHashRefreshOrInsert\n"));
  356. // We will return NULL and we are not holding the lock.
  357. }
  358. else
  359. {
  360. PHASH_TABLE_ENTRY pSneakedEntry;
  361. // Fill in the new entry
  362. pRetVal->LastSeen = CurrentTime;
  363. BrdgHashCopyKey( pRetVal->key, pKey, pTable->keySize );
  364. // We will need a write lock to add the entry
  365. NdisAcquireReadWriteLock(&pTable->tableLock, TRUE /*Read-Write*/, pLockState);
  366. // An entry could have been made between the release of the read lock
  367. // and the acquisition of the write lock. Check for this.
  368. pSneakedEntry = BrdgHashInternalFindEntry(pTable, pKey);
  369. if( pSneakedEntry != NULL )
  370. {
  371. // Someone snuck in with a new entry for this key.
  372. // This code path should be unusual. Just refresh the entry's values.
  373. InterlockedExchangeULong( &pSneakedEntry->LastSeen, CurrentTime );
  374. // Ditch the tentatively allocated new entry
  375. NdisFreeToNPagedLookasideList( &pTable->entryPool, pRetVal );
  376. // We will return the sneaked entry and the caller can refresh it
  377. pRetVal = pSneakedEntry;
  378. *pIsNewEntry = FALSE;
  379. }
  380. else
  381. {
  382. // Nobody snuck in between the lock release and acquire to make a new
  383. // entry for this key. Link in the new entry we alloced above.
  384. hash = (*pTable->pHashFunction)(pKey);
  385. // Insert at the head of the bucket's list
  386. pRetVal->Next = pTable->pBuckets[hash];
  387. pTable->pBuckets[hash] = pRetVal;
  388. #if DBG
  389. pTable->bucketSizes[hash]++;
  390. #endif
  391. pTable->numEntries++;
  392. // We will return the new entry, which the caller will initialize.
  393. *pIsNewEntry = TRUE;
  394. }
  395. // Return without the lock to let the user initialize or update the entry
  396. }
  397. }
  398. }
  399. return pRetVal;
  400. }
  401. VOID
  402. BrdgHashRemoveMatching(
  403. IN PHASH_TABLE pTable,
  404. IN PHASH_MATCH_FUNCTION pMatchFunc,
  405. PVOID pData
  406. )
  407. /*++
  408. Routine Description:
  409. Deletes all table entries that match, according to a supplied matching
  410. function. This should be called sparingly as it requies walking the
  411. entire table with a write lock held.
  412. Arguments:
  413. pTable The table
  414. pMatchFunc A function that return TRUE if an entry meets
  415. its criteria or FALSE otherwise
  416. pData A cookie to pass to pMatchFunc
  417. Return Value:
  418. None
  419. --*/
  420. {
  421. PHASH_TABLE_ENTRY pEntry, *pPrevPtr;
  422. ULONG i;
  423. LOCK_STATE LockState;
  424. NdisAcquireReadWriteLock( &pTable->tableLock, TRUE /*Write access*/, &LockState);
  425. for (i = 0; i < pTable->numBuckets; i++)
  426. {
  427. pEntry = pTable->pBuckets[i];
  428. pPrevPtr = &pTable->pBuckets[i];
  429. while( pEntry != NULL )
  430. {
  431. if( (*pMatchFunc)(pEntry, pData) )
  432. {
  433. PHASH_TABLE_ENTRY pNextEntry;
  434. pNextEntry = pEntry->Next;
  435. // Remove from the list
  436. SAFEASSERT( pPrevPtr != NULL );
  437. *pPrevPtr = pEntry->Next;
  438. // Deallocate
  439. NdisFreeToNPagedLookasideList( &pTable->entryPool, pEntry );
  440. pEntry = pNextEntry;
  441. #if DBG
  442. pTable->bucketSizes[i]--;
  443. #endif
  444. SAFEASSERT( pTable->numEntries >= 1L );
  445. pTable->numEntries--;
  446. }
  447. else
  448. {
  449. pPrevPtr = &pEntry->Next;
  450. pEntry = pEntry->Next;
  451. }
  452. }
  453. }
  454. NdisReleaseReadWriteLock( &pTable->tableLock, &LockState );
  455. }
  456. ULONG
  457. BrdgHashCopyMatching(
  458. IN PHASH_TABLE pTable,
  459. IN PHASH_MATCH_FUNCTION pMatchFunc,
  460. IN PHASH_COPY_FUNCTION pCopyFunction,
  461. IN ULONG copyUnitSize,
  462. IN PVOID pData,
  463. IN PUCHAR pBuffer,
  464. IN ULONG BufferLength
  465. )
  466. /*++
  467. Routine Description:
  468. Copies data out of table entries that meet certain criteria into a buffer.
  469. This should be called sparingly, as it requires walking the entire
  470. table (albeit with only a read lock held).
  471. Arguments:
  472. pTable The table
  473. pMatchFunc A function that returns TRUE if it is
  474. interested in copying data out of an
  475. entry and FALSE otherwise
  476. pCopyFunction A function that copies whatever it is
  477. interested in out of a chosen entry
  478. and into a data buffer
  479. copyUnitSize The size of the data copied out of each entry
  480. pData A cookie to pass to the two supplied functions
  481. pBuffer A buffer to copy into
  482. BufferLength Room available at pBuffer
  483. Return Value:
  484. The number of bytes necessary to store all matching data. If the returned value is
  485. <= BufferLength, all entries were written to pBuffer.
  486. If the returned value is > BufferLength, BufferLength - (BufferLength % copyUnitSize)
  487. bytes were written to pBuffer and there are additional entries that did not fit.
  488. --*/
  489. {
  490. PHASH_TABLE_ENTRY pEntry;
  491. ULONG i;
  492. LOCK_STATE LockState;
  493. ULONG EntryLimit, WrittenEntries, TotalEntries;
  494. EntryLimit = BufferLength / copyUnitSize;
  495. WrittenEntries = TotalEntries = 0L;
  496. NdisAcquireReadWriteLock( &pTable->tableLock, FALSE/*Read only*/, &LockState);
  497. for (i = 0L; i < pTable->numBuckets; i++)
  498. {
  499. pEntry = pTable->pBuckets[i];
  500. while( pEntry != NULL )
  501. {
  502. if( (*pMatchFunc)(pEntry, pData) )
  503. {
  504. if( WrittenEntries < EntryLimit )
  505. {
  506. (*pCopyFunction)(pEntry, pBuffer);
  507. pBuffer += copyUnitSize;
  508. WrittenEntries++;
  509. }
  510. TotalEntries++;
  511. }
  512. pEntry = pEntry->Next;
  513. }
  514. }
  515. NdisReleaseReadWriteLock( &pTable->tableLock, &LockState );
  516. return TotalEntries * copyUnitSize;
  517. }
  518. VOID
  519. BrdgHashPrefixMultiMatch(
  520. IN PHASH_TABLE pTable,
  521. IN PUCHAR pPrefixKey,
  522. IN UINT prefixLen,
  523. IN PMULTIMATCH_FUNC pFunc,
  524. IN PVOID pData
  525. )
  526. /*++
  527. Routine Description:
  528. Locates all table entries whose keys BEGIN with the given key
  529. prefix and calls pFunc for each one.
  530. For this to work, the caller must have previously set up the
  531. hash table with a hash function that uses only the prefix portion
  532. of the keys for hashing (i.e., this function relies on all the
  533. desired entries being in the same hash bucket).
  534. Arguments:
  535. pTable The table
  536. pPrefixKey The key prefix
  537. prefixLen The length of the prefix
  538. pFunc A function to call for each match
  539. pData An argument to pass to pFunc
  540. Return Value:
  541. None.
  542. --*/
  543. {
  544. ULONG hash = (*pTable->pHashFunction)(pPrefixKey);
  545. PHASH_TABLE_ENTRY pEntry = NULL;
  546. LOCK_STATE LockState;
  547. NdisAcquireReadWriteLock( &pTable->tableLock, FALSE /*Read only*/, &LockState );
  548. SAFEASSERT( hash < pTable->numBuckets );
  549. SAFEASSERT( prefixLen <= pTable->keySize );
  550. pEntry = pTable->pBuckets[hash];
  551. while( pEntry != NULL )
  552. {
  553. // Check if the prefix of the key matches
  554. if( BrdgHashKeysAreEqual(pEntry->key, pPrefixKey, prefixLen) )
  555. {
  556. (*pFunc)(pEntry, pData);
  557. }
  558. pEntry = pEntry->Next;
  559. }
  560. NdisReleaseReadWriteLock( &pTable->tableLock, &LockState );
  561. }
  562. // ===========================================================================
  563. //
  564. // PRIVATE FUNCTIONS
  565. //
  566. // ===========================================================================
  567. PHASH_TABLE_ENTRY
  568. BrdgHashInternalFindEntry(
  569. IN PHASH_TABLE pTable,
  570. IN PUCHAR pKey
  571. )
  572. /*++
  573. Routine Description:
  574. Locates the table entry with a given key
  575. CALLER IS RESPONSIBLE FOR OBTAINING THE TABLE LOCK
  576. Arguments:
  577. pTable The table
  578. pKey The key to locate
  579. Return Value:
  580. The entry matching the given key or NULL if none was found.
  581. --*/
  582. {
  583. ULONG hash = (*pTable->pHashFunction)(pKey);
  584. PHASH_TABLE_ENTRY pEntry = NULL, pFoundEntry = NULL;
  585. SAFEASSERT( hash < pTable->numBuckets );
  586. pEntry = pTable->pBuckets[hash];
  587. while( pEntry != NULL )
  588. {
  589. if( BrdgHashKeysAreEqual(pEntry->key, pKey, pTable->keySize) )
  590. {
  591. pFoundEntry = pEntry;
  592. break;
  593. }
  594. pEntry = pEntry->Next;
  595. }
  596. return pEntry;
  597. }
  598. VOID
  599. BrdgHashTimer(
  600. IN PVOID tablePointer
  601. )
  602. /*++
  603. Routine Description:
  604. This function is called periodically (currently every 10 seconds)
  605. to age out table entries.
  606. The function checks after traversing each bucket whether it has
  607. examined more than MAX_TIMER_EXAMINES. If it has, it exits. The bucket
  608. to be examined on the next invocation is stored in the nextTimerBucket
  609. field of the hash table..
  610. This can still result in a worst-case of the timer function examining
  611. an unbounded number of entries, but if the table entries are reasonably
  612. well balanced and the order of the number of entries is the same or less
  613. as the order of MAX_TIMER_EXAMINES, the timer function should limit
  614. itself to a number of examines resembling MAX_TIMER_EXAMINES per
  615. invocation.
  616. Arguments:
  617. tablePointer A pointer to the table to traverse
  618. All others Ignored
  619. Return Value:
  620. None
  621. --*/
  622. {
  623. PHASH_TABLE pTable = (PHASH_TABLE)tablePointer;
  624. PHASH_TABLE_ENTRY pEntry, *pPrevPtr;
  625. ULONG i, seenEntries = 0L;
  626. LOCK_STATE LockState;
  627. // Get write access to the table
  628. NdisAcquireReadWriteLock( &pTable->tableLock, TRUE /*Read-Write*/, &LockState);
  629. if( pTable->nextTimerBucket >= pTable->numBuckets )
  630. {
  631. // Start again at the beginning
  632. pTable->nextTimerBucket = 0L;
  633. }
  634. for (i = pTable->nextTimerBucket; i < pTable->numBuckets; i++)
  635. {
  636. pEntry = pTable->pBuckets[i];
  637. pPrevPtr = &pTable->pBuckets[i];
  638. while( pEntry != NULL )
  639. {
  640. ULONG LastSeen = pEntry->LastSeen;
  641. ULONG CurrentTime;
  642. // Always read the current time after reading LastSeen, so we know
  643. // CurrentTime > LastSeen.
  644. NdisGetSystemUpTime( &CurrentTime );
  645. // There is no sensible maximum removal time for hash table entries
  646. if( BrdgDeltaSafe(LastSeen, CurrentTime, MAXULONG) >= pTable->timeoutAge )
  647. {
  648. // Entry is too old. Remove it.
  649. PHASH_TABLE_ENTRY pNextEntry = pEntry->Next;
  650. SAFEASSERT( pPrevPtr != NULL );
  651. // Remove from list
  652. *pPrevPtr = pNextEntry;
  653. NdisFreeToNPagedLookasideList( &pTable->entryPool, pEntry );
  654. pEntry = pNextEntry;
  655. #if DBG
  656. pTable->bucketSizes[i]--;
  657. #endif
  658. SAFEASSERT( pTable->numEntries >= 1L );
  659. pTable->numEntries--;
  660. }
  661. else
  662. {
  663. pPrevPtr = &pEntry->Next;
  664. pEntry = pEntry->Next;
  665. }
  666. seenEntries++;
  667. }
  668. pTable->nextTimerBucket = i + 1;
  669. if( seenEntries >= MAX_TIMER_EXAMINES )
  670. {
  671. // We've looked at too many table entries. Bail out.
  672. break;
  673. }
  674. }
  675. NdisReleaseReadWriteLock( &pTable->tableLock, &LockState );
  676. }