diff options
author | Patrick Williams <iawillia@us.ibm.com> | 2012-10-31 16:01:11 -0500 |
---|---|---|
committer | A. Patrick Williams III <iawillia@us.ibm.com> | 2012-12-14 10:18:59 -0600 |
commit | da3888270ff596441bf78535c26dee0a7d923145 (patch) | |
tree | 019b88ce38e87b8346e0ef659f058556e26dec42 /src/usr/trace/buffer.C | |
parent | 6d7290eca2b0e753d1b954a56e2c82284dd23eb9 (diff) | |
download | talos-hostboot-da3888270ff596441bf78535c26dee0a7d923145.tar.gz talos-hostboot-da3888270ff596441bf78535c26dee0a7d923145.zip |
Lockless trace implementation
RTC: 35396
Change-Id: I96ea0d95606f04abb4dc2b0470345ca475b53912
Reviewed-on: http://gfw160.austin.ibm.com:8080/gerrit/2520
Tested-by: Jenkins Server
Reviewed-by: Daniel M. Crowell <dcrowell@us.ibm.com>
Reviewed-by: A. Patrick Williams III <iawillia@us.ibm.com>
Diffstat (limited to 'src/usr/trace/buffer.C')
-rw-r--r-- | src/usr/trace/buffer.C | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/src/usr/trace/buffer.C b/src/usr/trace/buffer.C new file mode 100644 index 000000000..921b5f2a4 --- /dev/null +++ b/src/usr/trace/buffer.C @@ -0,0 +1,588 @@ +/* IBM_PROLOG_BEGIN_TAG */ +/* This is an automatically generated prolog. */ +/* */ +/* $Source: src/usr/trace/buffer.C $ */ +/* */ +/* IBM CONFIDENTIAL */ +/* */ +/* COPYRIGHT International Business Machines Corp. 2012 */ +/* */ +/* p1 */ +/* */ +/* Object Code Only (OCO) source materials */ +/* Licensed Internal Code Source Materials */ +/* IBM HostBoot Licensed Internal Code */ +/* */ +/* The source code for this program is not published or otherwise */ +/* divested of its trade secrets, irrespective of what has been */ +/* deposited with the U.S. Copyright Office. */ +/* */ +/* Origin: 30 */ +/* */ +/* IBM_PROLOG_END_TAG */ +#include "buffer.H" +#include "bufferpage.H" +#include "entry.H" +#include "compdesc.H" +#include "daemonif.H" + +#include <assert.h> +#include <limits.h> +#include <string.h> +#include <util/align.H> + +namespace TRACE +{ + Buffer::Buffer(DaemonIf* i_daemon, size_t i_maxPages) : + iv_pagesAlloc(0), iv_pagesMax(i_maxPages), + iv_firstPage(NULL), iv_daemon(i_daemon) + { + iv_counters.totals = 0; + assert(i_maxPages > 0); + } + + void Buffer::_producerEnter() + { + locklessCounter value; + do + { + // Read current count. + value = iv_counters; + + // If there is a consumer (daemon) present, must wait. + while (value.consumerCount != 0) + { + futex_wait(&iv_counters.totals, value.totals); + value = iv_counters; + } + + // No consumers currently present, increment the producer count + // (to include us). + locklessCounter newValue = value; + newValue.producerCount++; + + // Attempt to atomically update the count. + if (__sync_bool_compare_and_swap(&iv_counters.totals, + value.totals, + newValue.totals) + ) + { + // Successful at atomic update, we're included in the count + // so we can exit the loop. + break; + } + + // Failed to update count, so try again. + } while(1); + } + + void Buffer::_producerExit() + { + locklessCounter value; + do + { + // Read current count. + value = iv_counters; + + // Decrement count to remove us. + locklessCounter newValue = value; + newValue.producerCount--; + + // Attempt to atomically update count. + if (!__sync_bool_compare_and_swap(&iv_counters.totals, + value.totals, + newValue.totals)) + { + // Failed, try again. + continue; + } + + // If we're the last producer and there is a consumer waiting, + // signal the consumer. + if((newValue.producerCount == 0) && (newValue.consumerCount != 0)) + { + futex_wake(&iv_counters.totals, UINT64_MAX); + } + + // If we're here, we are successful, so exit the loop. + break; + + } while(1); + } + + void Buffer::_consumerEnter() + { + locklessCounter value; + do + { + // Read current count. + value = iv_counters; + + // Set us up as a pending consumer. + locklessCounter newValue = value; + newValue.consumerCount = 1; + + // Attempt to atomically update counts. + if (!__sync_bool_compare_and_swap(&iv_counters.totals, + value.totals, + newValue.totals)) + { + // Failed, try again. + continue; + } + + // If there were producers waiting, we need to wait for them + // to clear out. + if (0 != newValue.producerCount) + { + do + { + futex_wait(&iv_counters.totals, newValue.totals); + newValue = iv_counters; + + } while(newValue.producerCount != 0); + } + + // If we're here, we are successful, so exit the loop. + break; + + } while(1); + } + + void Buffer::_consumerExit() + { + locklessCounter value; + do + { + // Read current count. + value = iv_counters; + + // Remove ourself as the consumer. + locklessCounter newValue = value; + newValue.consumerCount = 0; + + // Atomically update the count. + if (!__sync_bool_compare_and_swap(&iv_counters.totals, + value.totals, + newValue.totals)) + { + // Failed, try again. + continue; + } + + // Successful. Signal any producers that might be waiting and + // exit the loop. + futex_wake(&iv_counters.totals, UINT64_MAX); + break; + + } while(1); + } + + + Entry* Buffer::claimEntry(ComponentDesc* i_comp, size_t i_size) + { + // Bump up the needed size to include the entry itself and + // necessary alignment. + // (Alignment is needed so that Entry's members can be atomically + // updated). + i_size = ALIGN_8(i_size + sizeof(Entry)); + + // If an entry is bigger than this amount, it can never be satisfied. + if (i_size > (PAGESIZE - sizeof(BufferPage))) + { + return NULL; + } + + // During the process of claiming an entry, the daemon must be + // blocked out from tampering with the pages. + _producerEnter(); + + Entry* l_entry = NULL; + + // No we begin the search for an entry. + do + { + BufferPage* first = iv_firstPage; + + // Attempt to claim from the current page first. + if (first) + { + l_entry = first->claimEntry(i_size); + if (l_entry) + { + // Found one, great! We're done. + break; + } + + // If there is a "next" page, another thread has already + // allocated a new page, so just wait for it to show up. + // (as in, some other thread is going to update iv_firstPage) + if (first->next) + { + continue; + } + } + + // Wasn't enough space, so try to allocate a new page. + uint64_t pagesAllocated = iv_pagesAlloc; + if (pagesAllocated >= iv_pagesMax) + { + // Not enough pages. Wait until someone frees one. + _producerExit(); + iv_daemon->signal(); + futex_wait(reinterpret_cast<uint64_t*>( + const_cast<BufferPage**>(&iv_firstPage)), + reinterpret_cast<uint64_t>(first)); + _producerEnter(); + // A page might be allocated now, start over. + continue; + } + + // Atomically update page count. + uint64_t newPagesAllocated = pagesAllocated + 1; + if (!__sync_bool_compare_and_swap(&iv_pagesAlloc, + pagesAllocated, + newPagesAllocated)) + { + // Someone beat us to updating the count, so start over. + // (another thread is also trying to allocate a page, let them + // do it). + continue; + } + + // Successfully updated the count so allocate the new page. + BufferPage* newPage = BufferPage::allocate(); + newPage->prev = first; + + // If there is a page already, update its next pointer to point + // back at this new page. + if (first) + { + if (!__sync_bool_compare_and_swap(&first->next, + NULL, + newPage)) + { + // Someone beat us to allocating the page, release it. + BufferPage::deallocate(newPage); + do + { + pagesAllocated = iv_pagesAlloc; + newPagesAllocated = pagesAllocated - 1; + } + while (!__sync_bool_compare_and_swap(&iv_pagesAlloc, + pagesAllocated, + newPagesAllocated)); + continue; + } + } + + // Now we have a page allocated, claim our entry first and then + // hook it up to master list. + l_entry = newPage->claimEntry(i_size); + + if (!__sync_bool_compare_and_swap(&iv_firstPage, + first, + newPage)) + { + // We got beat adding page to the master list, so release it + // and use that page. + BufferPage::deallocate(newPage); + do + { + pagesAllocated = iv_pagesAlloc; + newPagesAllocated = pagesAllocated - 1; + } + while (!__sync_bool_compare_and_swap(&iv_pagesAlloc, + pagesAllocated, + newPagesAllocated)); + + // The entry we claimed out of the "new" page is no longer + // valid since we've freed it. + l_entry = NULL; + continue; + } + + // Since we allocated a page, signal any other tasks that might be + // blocked waiting for a page to show up. + futex_wake(reinterpret_cast<uint64_t*>( + const_cast<BufferPage**>(&iv_firstPage)), + UINT64_MAX); + + // And since we allocated a page, wake up the daemon if we + // allocated the last page or if there are more than 4 pages + // and we are an infinite buffer. (4 pages is arbitrary). + static const size_t SIGNAL_AFTER_N_PAGES_ALLOCATED = 4; + if ((pagesAllocated == iv_pagesMax) || + ((pagesAllocated >= SIGNAL_AFTER_N_PAGES_ALLOCATED) && + (iv_pagesMax == UNLIMITED))) + { + iv_daemon->signal(); + } + + + } while(!l_entry); + + // Update component name and entry size. + l_entry->comp = i_comp; + l_entry->size = i_size - sizeof(Entry); + + // Leave critical section. + _producerExit(); + + return l_entry; + } + + BufferPage* Buffer::claimPages() + { + // Enter critical daemon section. + _consumerEnter(); + + // Take page(s) from buffer. + BufferPage* page = iv_firstPage; + iv_firstPage = NULL; + iv_pagesAlloc = 0; + + // Rewind to beginning of buffer. + if (page) + { + while(page->prev) { page = page->prev; } + } + + // Signal producers that might be waiting for pages to free up. + futex_wake(reinterpret_cast<uint64_t*>( + const_cast<BufferPage**>(&iv_firstPage)), + UINT64_MAX); + + // Exit critical daemon section. + _consumerExit(); + + return page; + } + + void Buffer::commitEntry(Entry* i_entry) + { + // Prevent daemon from making updates while we're in here. + _producerEnter(); + + // Read the component from the entry itself (added as part of claiming). + ComponentDesc* l_comp = i_entry->comp; + + // Lockless loop to update component linked-list. + do + { + // Update our entry's "next" pointer. + i_entry->next = l_comp->iv_first; + + // If there is an entry, update its "prev" pointer to this entry. + if (i_entry->next) + { + if (!__sync_bool_compare_and_swap(&i_entry->next->prev, + NULL, + i_entry)) + { + // Failed lockless update, try again. + continue; + } + } + // Otherwise, we need to update the component list since this is + // the first entry. + else + { + if (!__sync_bool_compare_and_swap(&l_comp->iv_last, + NULL, + i_entry)) + { + // Failed lockless update, try again. + continue; + } + } + + // Successful at our updates, break out. + break; + + } while(1); + + // We just added the newest entry, so update component list. + // + // It is possible that the committer of the preceeding entry was + // "slow", so we need to keep trying to update until the current + // compoment "first" points at the preceeding entry and then make + // it point at this entry. + // + while (!__sync_bool_compare_and_swap(&l_comp->iv_first, + i_entry->next, + i_entry)); + + + // Atomically increment component size. + __sync_add_and_fetch(&l_comp->iv_curSize, i_entry->size); + + // Mark entry as committed. + i_entry->committed = 1; + + // All done, release for daemon. + _producerExit(); + + } + + size_t Buffer::getTrace(ComponentDesc* i_comp, void* o_data, size_t i_size) + { + char* l_data = reinterpret_cast<char*>(o_data); + size_t l_size = 0; + size_t l_entries = 0; + + // If either the pointer is null or the buffer is 0, we're just trying + // to determine the size of the buffer. + bool determineSize = ((o_data == NULL) || (i_size == 0)); + + if (determineSize) + { + i_size = UINT64_MAX; + } + + trace_buf_head_t* header = NULL; + + // If we're actually extracting, add the fsp-trace buffer header. + if(!determineSize) + { + if (i_size < sizeof(trace_buf_head_t)) + return 0; + + header = reinterpret_cast<trace_buf_head_t*>(&l_data[l_size]); + memset(header, '\0', sizeof(trace_buf_head_t)); + + header->ver = TRACE_BUF_VERSION; + header->hdr_len = sizeof(trace_buf_head_t); + header->time_flg = TRACE_TIME_REAL; + header->endian_flg = 'B'; // Big Endian. + memcpy(&header->comp[0], &i_comp->iv_compName, TRAC_COMP_SIZE); + } + l_size += sizeof(trace_buf_head_t); + + // Prevent daemon from changing things while we're extracting. + _producerEnter(); + + size_t l_totalSize = l_size; + Entry* entry = i_comp->iv_first; + Entry* orig_entry = entry; + + do + { + if ((!entry) || (!entry->comp)) + { + break; + } + + // First walk the list backwards to find everything that will fit. + while(1) + { + // fsp-trace buffer entries have an extra word of size at the + // end. That is where the sizeof(uint32_t) comes from... + + if ((l_totalSize + entry->size + sizeof(uint32_t)) <= i_size) + { + l_totalSize += entry->size + sizeof(uint32_t); + + if ((entry->next) && + (entry->next->comp)) + { + entry = entry->next; + continue; + } + } + break; + } + + // If we didn't find anything that fit, leave. + if (l_totalSize == l_size) + { + break; + } + + // If we're just trying to find the size, we're done. + if(determineSize) + { + l_size = l_totalSize; + break; + } + + // Now we can actually copy all the entries... + while(1) + { + // Copy entry data. + memcpy(&l_data[l_size], &entry->data[0],entry->size); + l_size += entry->size; + + // Copy entry size. + uint32_t entry_size = entry->size + sizeof(uint32_t); + memcpy(&l_data[l_size], &entry_size, sizeof(uint32_t)); + l_size += sizeof(uint32_t); + + l_entries++; + + if (entry == orig_entry) + { + break; + } + else + { + entry = entry->prev; + } + }; + + } + while(0); + + // Unlock for daemon. + _producerExit(); + + // Update header. + if (header) + { + header->size = l_size; + header->next_free = l_size; + header->te_count = l_entries; + } + + return l_size; + } + + bool Buffer::consumerOp(Entry** i_condAddr, Entry* i_condVal, + Entry** i_condActAddr, + Entry* i_condActVal, + Entry** i_addr, Entry* i_val) + { + bool rc = true; + + // Get consumer lock. + _consumerEnter(); + + // Primative #1. + if (NULL != i_condAddr) + { + if (i_condVal == *i_condAddr) + { + if (NULL != i_condActAddr) + { + *i_condActAddr = i_condActVal; + } + } + else + { + rc = false; + } + } + + // Primative #2. + if (NULL != i_addr) + { + *i_addr = i_val; + } + + // Release consumer lock. + _consumerExit(); + + return rc; + } + +} |