| /*************************************************************************/ /*! |
| @File htb_debug.c |
| @Title Debug Functionality |
| @Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved |
| @Description Provides kernel side debugFS Functionality. |
| @License Dual MIT/GPLv2 |
| |
| The contents of this file are subject to the MIT license as set out below. |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in |
| all copies or substantial portions of the Software. |
| |
| Alternatively, the contents of this file may be used under the terms of |
| the GNU General Public License Version 2 ("GPL") in which case the provisions |
| of GPL are applicable instead of those above. |
| |
| If you wish to allow use of your version of this file only under the terms of |
| GPL, and not to allow others to use your version of this file under the terms |
| of the MIT license, indicate your decision by deleting the provisions above |
| and replace them with the notice and other provisions required by GPL as set |
| out in the file called "GPL-COPYING" included in this distribution. If you do |
| not delete the provisions above, a recipient may use your version of this file |
| under the terms of either the MIT license or GPL. |
| |
| This License is also included in this distribution in the file called |
| "MIT-COPYING". |
| |
| EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS |
| PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING |
| BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
| PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR |
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ /**************************************************************************/ |
| #include "rgxdevice.h" |
| #include "htbserver.h" |
| #include "htbuffer.h" |
| #include "htbuffer_types.h" |
| #include "tlstream.h" |
| #include "tlclient.h" |
| #include "pvrsrv_tlcommon.h" |
| #include "pvr_debugfs.h" |
| #include "img_types.h" |
| #include "img_defs.h" |
| #include "pvrsrv_error.h" |
| #include "osfunc.h" |
| #include "allocmem.h" |
| #include "pvr_notifier.h" |
| #include "pvrsrv.h" |
| #include "htb_debug.h" |
| #include "kernel_compatibility.h" |
| |
| // Global data handles for buffer manipulation and processing |
| typedef struct |
| { |
| PPVR_DEBUGFS_ENTRY_DATA psDumpHostDebugFSEntry; /* debugFS entry hook */ |
| IMG_HANDLE hStream; /* Stream handle for debugFS use */ |
| } HTB_DBG_INFO; |
| |
| static HTB_DBG_INFO g_sHTBData; |
| |
| // Enable for extra debug level |
| //#define HTB_CHATTY 1 |
| |
| /****************************************************************************** |
| * debugFS display routines |
| *****************************************************************************/ |
| static int HTBDumpBuffer(DUMPDEBUG_PRINTF_FUNC *, void *, void *); |
| static void _HBTraceSeqPrintf(void *, const IMG_CHAR *, ...); |
| static int _DebugHBTraceSeqShow(struct seq_file *, void *); |
| static void *_DebugHBTraceSeqStart(struct seq_file *, loff_t *); |
| static void _DebugHBTraceSeqStop(struct seq_file *, void *); |
| static void *_DebugHBTraceSeqNext(struct seq_file *, void *, loff_t *); |
| |
| static void _HBTraceSeqPrintf(void *pvDumpDebugFile, |
| const IMG_CHAR *pszFormat, ...) |
| { |
| struct seq_file *psSeqFile = (struct seq_file *)pvDumpDebugFile; |
| va_list ArgList; |
| |
| va_start(ArgList, pszFormat); |
| seq_vprintf(psSeqFile, pszFormat, ArgList); |
| va_end(ArgList); |
| } |
| |
| static int _DebugHBTraceSeqShow(struct seq_file *psSeqFile, void *pvData) |
| { |
| int retVal; |
| |
| PVR_ASSERT(NULL != psSeqFile); |
| |
| /* psSeqFile should never be NULL */ |
| if (psSeqFile == NULL) |
| { |
| return -1; |
| } |
| |
| /* |
| * Ensure that we have a valid address to use to dump info from. If NULL we |
| * return a failure code to terminate the seq_read() call. pvData is either |
| * SEQ_START_TOKEN (for the initial call) or an HTB buffer address for |
| * subsequent calls [returned from the NEXT function]. |
| */ |
| if (pvData == NULL) |
| { |
| return -1; |
| } |
| |
| |
| retVal = HTBDumpBuffer(_HBTraceSeqPrintf, psSeqFile, pvData); |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Returning %d", __func__, retVal)); |
| #endif /* HTB_CHATTY */ |
| |
| return retVal; |
| } |
| |
| typedef struct { |
| IMG_PBYTE pBuf; /* Raw data buffer from TL stream */ |
| IMG_UINT32 uiBufLen; /* Amount of data to process from 'pBuf' */ |
| IMG_UINT32 uiTotal; /* Total bytes processed */ |
| IMG_UINT32 uiMsgLen; /* Length of HTB message to be processed */ |
| IMG_PBYTE pCurr; /* pointer to current message to be decoded */ |
| IMG_CHAR szBuffer[PVR_MAX_DEBUG_MESSAGE_LEN]; /* Output string */ |
| } HTB_Sentinel_t; |
| |
| static IMG_UINT32 idToLogIdx(IMG_UINT32); /* Forward declaration */ |
| |
| /* |
| * HTB_GetNextMessage |
| * |
| * Get next non-empty message block from the buffer held in pSentinel->pBuf |
| * If we exhaust the data buffer we refill it (after releasing the previous |
| * message(s) [only one non-NULL message, but PAD messages will get released |
| * as we traverse them]. |
| * |
| * Input: |
| * pSentinel references the already acquired data buffer |
| * |
| * Output: |
| * pSentinel |
| * -> uiMsglen updated to the size of the non-NULL message |
| * |
| * Returns: |
| * Address of first non-NULL message in the buffer (if any) |
| * NULL if there is no further data available from the stream and the buffer |
| * contents have been drained. |
| */ |
| static IMG_PBYTE HTB_GetNextMessage(HTB_Sentinel_t *); |
| static IMG_PBYTE HTB_GetNextMessage(HTB_Sentinel_t *pSentinel) |
| { |
| IMG_PBYTE pNext, pLast, pStart, pData = NULL; |
| IMG_PBYTE pCurrent; /* Current processing point within buffer */ |
| PVRSRVTL_PPACKETHDR ppHdr; /* Current packet header */ |
| IMG_UINT32 uiHdrType; /* Packet header type */ |
| IMG_UINT32 uiMsgSize; /* Message size of current packet (bytes) */ |
| IMG_UINT32 ui32DataSize; |
| IMG_UINT32 uiBufLen; |
| IMG_BOOL bUnrecognizedErrorPrinted = IMG_FALSE; |
| IMG_UINT32 ui32Data; |
| IMG_UINT32 ui32LogIdx; |
| PVRSRV_ERROR eError; |
| |
| PVR_ASSERT(NULL != pSentinel); |
| |
| uiBufLen = pSentinel->uiBufLen; |
| /* Convert from byte to uint32 size */ |
| ui32DataSize = pSentinel->uiBufLen / sizeof(IMG_UINT32); |
| |
| pLast = pSentinel->pBuf + pSentinel->uiBufLen; |
| |
| pStart = pSentinel->pBuf; |
| |
| pNext = pStart; |
| pSentinel->uiMsgLen = 0; // Reset count for this message |
| uiMsgSize = 0; // nothing processed so far |
| ui32LogIdx = HTB_SF_LAST; // Loop terminator condition |
| |
| do |
| { |
| /* |
| * If we've drained the buffer we must RELEASE and ACQUIRE some more. |
| */ |
| if (pNext >= pLast) |
| { |
| eError = TLClientReleaseData(DIRECT_BRIDGE_HANDLE, g_sHTBData.hStream); |
| PVR_ASSERT(eError == PVRSRV_OK); |
| |
| eError = TLClientAcquireData(DIRECT_BRIDGE_HANDLE, |
| g_sHTBData.hStream, &pSentinel->pBuf, &pSentinel->uiBufLen); |
| |
| if (PVRSRV_OK != eError) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s: %s FAILED '%s'", __func__, |
| "TLClientAcquireData", PVRSRVGETERRORSTRING(eError))); |
| return NULL; |
| } |
| |
| // Reset our limits - if we've returned an empty buffer we're done. |
| pLast = pSentinel->pBuf + pSentinel->uiBufLen; |
| pStart = pSentinel->pBuf; |
| pNext = pStart; |
| |
| if (pStart == NULL || pLast == NULL) |
| { |
| return NULL; |
| } |
| } |
| |
| /* |
| * We should have a header followed by data block(s) in the stream. |
| */ |
| |
| pCurrent = pNext; |
| ppHdr = GET_PACKET_HDR(pCurrent); |
| |
| if (ppHdr == NULL) |
| { |
| PVR_DPF((PVR_DBG_ERROR, |
| "%s: Unexpected NULL packet in Host Trace buffer", |
| __func__)); |
| pSentinel->uiMsgLen += uiMsgSize; |
| return NULL; // This should never happen |
| } |
| |
| /* |
| * This should *NEVER* fire. If it does it means we have got some |
| * dubious packet header back from the HTB stream. In this case |
| * the sensible thing is to abort processing and return to |
| * the caller |
| */ |
| uiHdrType = GET_PACKET_TYPE(ppHdr); |
| |
| PVR_ASSERT(uiHdrType < PVRSRVTL_PACKETTYPE_LAST && |
| uiHdrType > PVRSRVTL_PACKETTYPE_UNDEF); |
| |
| if (uiHdrType < PVRSRVTL_PACKETTYPE_LAST && |
| uiHdrType > PVRSRVTL_PACKETTYPE_UNDEF) |
| { |
| /* |
| * We have a (potentially) valid data header. We should see if |
| * the associated packet header matches one of our expected |
| * types. |
| */ |
| pNext = (IMG_PBYTE)GET_NEXT_PACKET_ADDR(ppHdr); |
| |
| PVR_ASSERT(pNext != NULL); |
| |
| uiMsgSize = (IMG_UINT32)((size_t)pNext - (size_t)ppHdr); |
| |
| pSentinel->uiMsgLen += uiMsgSize; |
| |
| pData = GET_PACKET_DATA_PTR(ppHdr); |
| |
| /* |
| * Handle non-DATA packet types. These include PAD fields which |
| * may have data associated and other types. We simply discard |
| * these as they have no decodable information within them. |
| */ |
| if (uiHdrType != PVRSRVTL_PACKETTYPE_DATA) |
| { |
| /* |
| * Now release the current non-data packet and proceed to the |
| * next entry (if any). |
| */ |
| eError = TLClientReleaseDataLess(DIRECT_BRIDGE_HANDLE, |
| g_sHTBData.hStream, uiMsgSize); |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Packet Type %x Length %u", |
| __func__, uiHdrType, uiMsgSize)); |
| #endif |
| |
| if (eError != PVRSRV_OK) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s: %s FAILED - '%s' message" |
| " size %u", __func__, "TLClientReleaseDataLess", |
| PVRSRVGETERRORSTRING(eError), uiMsgSize)); |
| } |
| |
| eError = TLClientAcquireData(DIRECT_BRIDGE_HANDLE, |
| g_sHTBData.hStream, &pSentinel->pBuf, &pSentinel->uiBufLen); |
| |
| if (PVRSRV_OK != eError) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s: %s FAILED - %s Giving up", |
| __func__, "TLClientAcquireData", |
| PVRSRVGETERRORSTRING(eError))); |
| |
| return NULL; |
| } |
| pSentinel->uiMsgLen = 0; |
| // Reset our limits - if we've returned an empty buffer we're done. |
| pLast = pSentinel->pBuf + pSentinel->uiBufLen; |
| pStart = pSentinel->pBuf; |
| pNext = pStart; |
| |
| if (pStart == NULL || pLast == NULL) |
| { |
| return NULL; |
| } |
| continue; |
| } |
| if (pData == NULL || pData >= pLast) |
| { |
| continue; |
| } |
| ui32Data = *(IMG_UINT32 *)pData; |
| ui32LogIdx = idToLogIdx(ui32Data); |
| } |
| else |
| { |
| PVR_DPF((PVR_DBG_WARNING, "Unexpected Header @%p value %x", |
| ppHdr, uiHdrType)); |
| |
| return NULL; |
| } |
| |
| /* |
| * Check if the unrecognized ID is valid and therefore, tracebuf |
| * needs updating. |
| */ |
| if (HTB_SF_LAST == ui32LogIdx && HTB_LOG_VALIDID(ui32Data) |
| && IMG_FALSE == bUnrecognizedErrorPrinted) |
| { |
| PVR_DPF((PVR_DBG_WARNING, |
| "%s: Unrecognised LOG value '%x' GID %x Params %d ID %x @ '%p'", |
| __func__, ui32Data, HTB_SF_GID(ui32Data), |
| HTB_SF_PARAMNUM(ui32Data), ui32Data & 0xfff, pData)); |
| bUnrecognizedErrorPrinted = IMG_FALSE; |
| } |
| |
| } while (HTB_SF_LAST == ui32LogIdx); |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Returning data @ %p Log value '%x'", |
| __func__, pCurrent, ui32Data)); |
| #endif /* HTB_CHATTY */ |
| |
| return pCurrent; |
| } |
| |
| /* |
| * HTB_GetFirstMessage |
| * |
| * Called from START to obtain the buffer address of the first message within |
| * pSentinel->pBuf. Will ACQUIRE data if the buffer is empty. |
| * |
| * Input: |
| * pSentinel |
| * puiPosition Offset within the debugFS file |
| * |
| * Output: |
| * pSentinel->pCurr Set to reference the first valid non-NULL message within |
| * the buffer. If no valid message is found set to NULL. |
| * pSentinel |
| * ->pBuf if unset on entry |
| * ->uiBufLen if pBuf unset on entry |
| * |
| * Side-effects: |
| * HTB TL stream will be updated to bypass any zero-length PAD messages before |
| * the first non-NULL message (if any). |
| */ |
| static void HTB_GetFirstMessage(HTB_Sentinel_t *, loff_t *); |
| static void HTB_GetFirstMessage(HTB_Sentinel_t *pSentinel, loff_t *puiPosition) |
| { |
| PVRSRV_ERROR eError; |
| |
| if (pSentinel == NULL) |
| return; |
| |
| if (pSentinel->pBuf == NULL) |
| { |
| /* Acquire data */ |
| pSentinel->uiMsgLen = 0; |
| |
| eError = TLClientAcquireData(DIRECT_BRIDGE_HANDLE, |
| g_sHTBData.hStream, &pSentinel->pBuf, &pSentinel->uiBufLen); |
| |
| if (PVRSRV_OK != eError) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s: %s FAILED '%s'", |
| __func__, "TLClientAcquireData", PVRSRVGETERRORSTRING(eError))); |
| |
| pSentinel->pBuf = NULL; |
| pSentinel->pCurr = NULL; |
| } |
| else |
| { |
| /* |
| * If there is no data available we set pSentinel->pCurr to NULL |
| * and return. This is expected behaviour if we've drained the |
| * data and nothing else has yet been produced. |
| */ |
| if (pSentinel->uiBufLen == 0 || pSentinel->pBuf == NULL) |
| { |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Empty Buffer @ %p", __func__, |
| pSentinel->pBuf)); |
| #endif /* HTB_CHATTY */ |
| pSentinel->pCurr = NULL; |
| return; |
| } |
| } |
| } |
| |
| /* Locate next message within buffer. NULL => no more data to process */ |
| pSentinel->pCurr = HTB_GetNextMessage(pSentinel); |
| } |
| |
| /* |
| * _DebugHBTraceSeqStart: |
| * |
| * Returns the address to use for subsequent 'Show', 'Next', 'Stop' file ops. |
| * Return SEQ_START_TOKEN for the very first call and allocate a sentinel for |
| * use by the 'Show' routine and its helpers. |
| * This is stored in the psSeqFile->private hook field. |
| * |
| * We obtain access to the TLstream associated with the HTB. If this doesn't |
| * exist (because no pvrdebug capture trace has been set) we simply return with |
| * a NULL value which will stop the seq_file traversal. |
| */ |
| static void *_DebugHBTraceSeqStart(struct seq_file *psSeqFile, |
| loff_t *puiPosition) |
| { |
| HTB_Sentinel_t *pSentinel = (HTB_Sentinel_t *)psSeqFile->private; |
| PVRSRV_ERROR eError; |
| IMG_UINT32 uiTLMode; |
| void *retVal; |
| IMG_HANDLE hStream; |
| |
| /* Open the stream in non-blocking mode so that we can determine if there |
| * is no data to consume. Also disable the producer callback (if any) and |
| * the open callback so that we do not generate spurious trace data when |
| * accessing the stream. |
| */ |
| uiTLMode = PVRSRV_STREAM_FLAG_ACQUIRE_NONBLOCKING| |
| PVRSRV_STREAM_FLAG_DISABLE_PRODUCER_CALLBACK| |
| PVRSRV_STREAM_FLAG_IGNORE_OPEN_CALLBACK; |
| |
| /* If two or more processes try to read from this file at the same time |
| * the TLClientOpenStream() function will handle this by allowing only |
| * one of them to actually open the stream. The other process will get |
| * an error stating that the stream is already open. The open function |
| * is threads safe. */ |
| eError = TLClientOpenStream(DIRECT_BRIDGE_HANDLE, HTB_STREAM_NAME, uiTLMode, |
| &hStream); |
| |
| if (PVRSRV_ERROR_ALREADY_OPEN == eError) |
| { |
| /* Stream allows only one reader so return error if it's already |
| * opened. */ |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Stream handle %p already exists for %s", |
| __func__, g_sHTBData.hStream, HTB_STREAM_NAME)); |
| #endif |
| return ERR_PTR(-EBUSY); |
| } |
| else if (PVRSRV_OK != eError) |
| { |
| /* |
| * No stream available so nothing to report |
| */ |
| return NULL; |
| } |
| |
| /* There is a window where hStream can be NULL but the stream is already |
| * opened. This shouldn't matter since the TLClientOpenStream() will make |
| * sure that only one stream can be opened and only one process can reach |
| * this place at a time. Also the .stop function will be always called |
| * after this function returns so there should be no risk of stream |
| * not being closed. */ |
| PVR_ASSERT(g_sHTBData.hStream == NULL); |
| g_sHTBData.hStream = hStream; |
| |
| /* |
| * Ensure we have our debug-specific data store allocated and hooked from |
| * our seq_file private data. |
| * If the allocation fails we can safely return NULL which will stop |
| * further calls from the seq_file routines (NULL return from START or NEXT |
| * means we have no (more) data to process) |
| */ |
| if (pSentinel == NULL) |
| { |
| pSentinel = (HTB_Sentinel_t *)OSAllocZMem(sizeof(HTB_Sentinel_t)); |
| psSeqFile->private = pSentinel; |
| } |
| |
| /* |
| * Find the first message location within pSentinel->pBuf |
| * => for SEQ_START_TOKEN we must issue our first ACQUIRE, also for the |
| * subsequent re-START calls (if any). |
| */ |
| |
| HTB_GetFirstMessage(pSentinel, puiPosition); |
| |
| if (*puiPosition == 0) |
| { |
| retVal = SEQ_START_TOKEN; |
| } |
| else |
| { |
| if (pSentinel == NULL) |
| { |
| retVal = NULL; |
| } |
| else |
| { |
| retVal = (void *)pSentinel->pCurr; |
| } |
| } |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Returning %p, Stream %s @ %p", __func__, |
| retVal, HTB_STREAM_NAME, g_sHTBData.hStream)); |
| #endif /* HTB_CHATTY */ |
| |
| return retVal; |
| |
| } |
| |
| /* |
| * _DebugTBTraceSeqStop: |
| * |
| * Stop processing data collection and release any previously allocated private |
| * data structure if we have exhausted the previously filled data buffers. |
| */ |
| static void _DebugHBTraceSeqStop(struct seq_file *psSeqFile, void *pvData) |
| { |
| HTB_Sentinel_t *pSentinel = (HTB_Sentinel_t *)psSeqFile->private; |
| IMG_UINT32 uiMsgLen; |
| |
| if (NULL == pSentinel) |
| return; |
| |
| uiMsgLen = pSentinel->uiMsgLen; |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: MsgLen = %d", __func__, uiMsgLen)); |
| #endif /* HTB_CHATTY */ |
| |
| /* If we get here the handle should never be NULL because |
| * _DebugHBTraceSeqStart() shouldn't allow that. */ |
| if (g_sHTBData.hStream != NULL) |
| { |
| PVRSRV_ERROR eError; |
| |
| if (uiMsgLen != 0) |
| { |
| eError = TLClientReleaseDataLess(DIRECT_BRIDGE_HANDLE, |
| g_sHTBData.hStream, uiMsgLen); |
| |
| if (PVRSRV_OK != eError) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s: %s FAILED - %s, nBytes %u", |
| __func__, "TLClientReleaseDataLess", |
| PVRSRVGETERRORSTRING(eError), uiMsgLen)); |
| } |
| } |
| |
| eError = TLClientCloseStream(DIRECT_BRIDGE_HANDLE, g_sHTBData.hStream); |
| if (PVRSRV_OK != eError) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s() failed (%s) in %s()", |
| "TLClientCloseStream", PVRSRVGETERRORSTRING(eError), |
| __func__)); |
| } |
| g_sHTBData.hStream = NULL; |
| } |
| |
| if (pSentinel != NULL) |
| { |
| psSeqFile->private = NULL; |
| OSFreeMem(pSentinel); |
| } |
| } |
| |
| |
| /* |
| * _DebugHBTraceSeqNext: |
| * |
| * This is where we release any acquired data which has been processed by the |
| * SeqShow routine. If we have encountered a seq_file overflow we stop |
| * processing and return NULL. Otherwise we release the message that we |
| * previously processed and simply update our position pointer to the next |
| * valid HTB message (if any) |
| */ |
| static void *_DebugHBTraceSeqNext(struct seq_file *psSeqFile, |
| void *pvData, |
| loff_t *puiPosition) |
| { |
| loff_t curPos; |
| HTB_Sentinel_t *pSentinel = (HTB_Sentinel_t *)psSeqFile->private; |
| PVRSRV_ERROR eError; |
| |
| PVR_UNREFERENCED_PARAMETER(pvData); |
| |
| if (puiPosition) |
| { |
| curPos = *puiPosition; |
| *puiPosition = curPos+1; |
| } |
| |
| /* |
| * Determine if we've had an overflow on the previous 'Show' call. If so |
| * we leave the previously acquired data in the queue (by releasing 0 bytes) |
| * and return NULL to end this seq_read() iteration. |
| * If we have not overflowed we simply get the next HTB message and use that |
| * for our display purposes |
| */ |
| |
| if (seq_has_overflowed(psSeqFile)) |
| { |
| (void)TLClientReleaseDataLess(DIRECT_BRIDGE_HANDLE, g_sHTBData.hStream, 0); |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: OVERFLOW - returning NULL", __func__)); |
| #endif /* HTB_CHATTY */ |
| |
| return (void *)NULL; |
| } |
| else |
| { |
| eError = TLClientReleaseDataLess(DIRECT_BRIDGE_HANDLE, g_sHTBData.hStream, |
| pSentinel->uiMsgLen); |
| |
| if (PVRSRV_OK != eError) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s: %s FAILED '%s' @ %p Length %d", |
| __func__, "TLClientReleaseDataLess", |
| PVRSRVGETERRORSTRING(eError), pSentinel->pCurr, |
| pSentinel->uiMsgLen)); |
| PVR_DPF((PVR_DBG_WARNING, "%s: Buffer @ %p..%p", __func__, |
| pSentinel->pBuf, |
| (IMG_PBYTE)(pSentinel->pBuf+pSentinel->uiBufLen))); |
| |
| } |
| |
| eError = TLClientAcquireData(DIRECT_BRIDGE_HANDLE, |
| g_sHTBData.hStream, &pSentinel->pBuf, &pSentinel->uiBufLen); |
| |
| if (PVRSRV_OK != eError) |
| { |
| PVR_DPF((PVR_DBG_WARNING, "%s: %s FAILED '%s'\nPrev message len %d", |
| __func__, "TLClientAcquireData", PVRSRVGETERRORSTRING(eError), |
| pSentinel->uiMsgLen)); |
| pSentinel->pBuf = NULL; |
| } |
| |
| pSentinel->uiMsgLen = 0; // We don't (yet) know the message size |
| } |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Returning %p Msglen %d", |
| __func__, pSentinel->pBuf, pSentinel->uiMsgLen)); |
| #endif /* HTB_CHATTY */ |
| |
| if (pSentinel->pBuf == NULL || pSentinel->uiBufLen == 0) |
| { |
| return NULL; |
| } |
| |
| pSentinel->pCurr = HTB_GetNextMessage(pSentinel); |
| |
| return pSentinel->pCurr; |
| } |
| |
| static const struct seq_operations gsHTBReadOps = { |
| .start = _DebugHBTraceSeqStart, |
| .stop = _DebugHBTraceSeqStop, |
| .next = _DebugHBTraceSeqNext, |
| .show = _DebugHBTraceSeqShow, |
| }; |
| |
| |
| /****************************************************************************** |
| * HTB Dumping routines and definitions |
| *****************************************************************************/ |
| #define IS_VALID_FMT_STRING(FMT) (strchr(FMT, '%') != NULL) |
| #define MAX_STRING_SIZE (128) |
| |
| typedef enum |
| { |
| TRACEBUF_ARG_TYPE_INT, |
| TRACEBUF_ARG_TYPE_ERR, |
| TRACEBUF_ARG_TYPE_NONE |
| } TRACEBUF_ARG_TYPE; |
| |
| /* |
| * Array of all Host Trace log IDs used to convert the tracebuf data |
| */ |
| typedef struct _HTB_TRACEBUF_LOG_ { |
| HTB_LOG_SFids eSFId; |
| IMG_CHAR *pszName; |
| IMG_CHAR *pszFmt; |
| IMG_UINT32 ui32ArgNum; |
| } HTB_TRACEBUF_LOG; |
| |
| static const HTB_TRACEBUF_LOG aLogs[] = { |
| #define X(a, b, c, d, e) {HTB_LOG_CREATESFID(a,b,e), #c, d, e}, |
| HTB_LOG_SFIDLIST |
| #undef X |
| }; |
| |
| static const IMG_CHAR *aGroups[] = { |
| #define X(A,B) #B, |
| HTB_LOG_SFGROUPLIST |
| #undef X |
| }; |
| static const IMG_UINT32 uiMax_aGroups = ARRAY_SIZE(aGroups) - 1; |
| |
| static TRACEBUF_ARG_TYPE ExtractOneArgFmt(IMG_CHAR **, IMG_CHAR *); |
| /* |
| * ExtractOneArgFmt |
| * |
| * Scan the input 'printf-like' string *ppszFmt and return the next |
| * value string to be displayed. If there is no '%' format field in the |
| * string we return 'TRACEBUF_ARG_TYPE_NONE' and leave the input string |
| * untouched. |
| * |
| * Input |
| * ppszFmt reference to format string to be decoded |
| * pszOneArgFmt single field format from *ppszFmt |
| * |
| * Returns |
| * TRACEBUF_ARG_TYPE_ERR unrecognised argument |
| * TRACEBUF_ARG_TYPE_INT variable is of numeric type |
| * TRACEBUF_ARG_TYPE_NONE no variable reference in *ppszFmt |
| * |
| * Side-effect |
| * *ppszFmt is updated to reference the next part of the format string |
| * to be scanned |
| */ |
| static TRACEBUF_ARG_TYPE ExtractOneArgFmt( |
| IMG_CHAR **ppszFmt, |
| IMG_CHAR *pszOneArgFmt) |
| { |
| IMG_CHAR *pszFmt; |
| IMG_CHAR *psT; |
| IMG_UINT32 ui32Count = MAX_STRING_SIZE; |
| IMG_UINT32 ui32OneArgSize; |
| TRACEBUF_ARG_TYPE eRet = TRACEBUF_ARG_TYPE_ERR; |
| |
| if (NULL == ppszFmt) |
| return TRACEBUF_ARG_TYPE_ERR; |
| |
| pszFmt = *ppszFmt; |
| if (NULL == pszFmt) |
| return TRACEBUF_ARG_TYPE_ERR; |
| |
| /* |
| * Find the first '%' |
| * NOTE: we can be passed a simple string to display which will have no |
| * parameters embedded within it. In this case we simply return |
| * TRACEBUF_ARG_TYPE_NONE and the string contents will be the full pszFmt |
| */ |
| psT = strchr(pszFmt, '%'); |
| if (psT == NULL) |
| { |
| return TRACEBUF_ARG_TYPE_NONE; |
| } |
| |
| /* Find next conversion identifier after the initial '%' */ |
| while ((*psT++) && (ui32Count-- > 0)) |
| { |
| switch (*psT) |
| { |
| case 'd': |
| case 'i': |
| case 'o': |
| case 'u': |
| case 'x': |
| case 'X': |
| { |
| eRet = TRACEBUF_ARG_TYPE_INT; |
| goto _found_arg; |
| } |
| case 's': |
| { |
| eRet = TRACEBUF_ARG_TYPE_ERR; |
| goto _found_arg; |
| } |
| } |
| } |
| |
| if ((psT == NULL) || (ui32Count == 0)) return TRACEBUF_ARG_TYPE_ERR; |
| |
| _found_arg: |
| ui32OneArgSize = psT - pszFmt + 1; |
| OSCachedMemCopy(pszOneArgFmt, pszFmt, ui32OneArgSize); |
| pszOneArgFmt[ui32OneArgSize] = '\0'; |
| |
| *ppszFmt = psT + 1; |
| |
| return eRet; |
| } |
| |
| static IMG_UINT32 idToLogIdx(IMG_UINT32 ui32CheckData) |
| { |
| IMG_UINT32 i = 0; |
| for (i = 0; aLogs[i].eSFId != HTB_SF_LAST; i++) |
| { |
| if ( ui32CheckData == aLogs[i].eSFId ) |
| return i; |
| } |
| /* Nothing found, return max value */ |
| return HTB_SF_LAST; |
| } |
| |
| /* |
| * DecodeHTB |
| * |
| * Decode the data buffer message located at pBuf. This should be a valid |
| * HTB message as we are provided with the start of the buffer. If empty there |
| * is no message to process. We update the uiMsgLen field with the size of the |
| * HTB message that we have processed so that it can be returned to the system |
| * on successful logging of the message to the output file. |
| * |
| * Input |
| * pSentinel reference to newly read data and pending completion data |
| * from a previous invocation [handle seq_file buffer overflow] |
| * -> pBuf reference to raw data that we are to parse |
| * -> uiBufLen total number of bytes of data available |
| * -> pCurr start of message to decode |
| * |
| * pvDumpDebugFile output file |
| * pfnDumpDebugPrintf output generating routine |
| * |
| * Output |
| * pSentinel |
| * -> uiMsgLen length of the decoded message which will be freed to |
| * the system on successful completion of the seq_file |
| * update via _DebugHBTraceSeqNext(), |
| * Return Value |
| * 0 successful decode |
| * -1 unsuccessful decode |
| */ |
| static int |
| DecodeHTB(HTB_Sentinel_t *pSentinel, |
| void *pvDumpDebugFile, DUMPDEBUG_PRINTF_FUNC *pfnDumpDebugPrintf) |
| { |
| IMG_UINT32 ui32Data, ui32LogIdx, ui32ArgsCur; |
| IMG_CHAR *pszFmt = NULL; |
| IMG_CHAR aszOneArgFmt[MAX_STRING_SIZE]; |
| IMG_BOOL bUnrecognizedErrorPrinted = IMG_FALSE; |
| |
| IMG_UINT32 ui32DataSize; |
| IMG_UINT32 uiBufLen = pSentinel->uiBufLen; |
| size_t nPrinted; |
| |
| IMG_PBYTE pNext, pLast, pStart, pData = NULL; |
| PVRSRVTL_PPACKETHDR ppHdr; /* Current packet header */ |
| IMG_UINT32 uiHdrType; /* Packet header type */ |
| IMG_UINT32 uiMsgSize; /* Message size of current packet (bytes) */ |
| IMG_BOOL bPacketsDropped; |
| |
| /* Convert from byte to uint32 size */ |
| ui32DataSize = uiBufLen / sizeof(IMG_UINT32); |
| |
| pLast = pSentinel->pBuf + pSentinel->uiBufLen; |
| pStart = pSentinel->pCurr; |
| |
| pNext = pStart; |
| pSentinel->uiMsgLen = 0; // Reset count for this message |
| uiMsgSize = 0; // nothing processed so far |
| ui32LogIdx = HTB_SF_LAST; // Loop terminator condition |
| |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: Buf @ %p..%p, Length = %d", __func__, |
| pStart, pLast, uiBufLen)); |
| #endif /* HTB_CHATTY */ |
| |
| /* |
| * We should have a DATA header with the necessary information following |
| */ |
| ppHdr = GET_PACKET_HDR(pStart); |
| |
| if (ppHdr == NULL) |
| { |
| PVR_DPF((PVR_DBG_ERROR, |
| "%s: Unexpected NULL packet in Host Trace buffer", __func__)); |
| return -1; |
| } |
| |
| uiHdrType = GET_PACKET_TYPE(ppHdr); |
| PVR_ASSERT(uiHdrType == PVRSRVTL_PACKETTYPE_DATA); |
| |
| pNext = (IMG_PBYTE)GET_NEXT_PACKET_ADDR(ppHdr); |
| |
| PVR_ASSERT(pNext != NULL); |
| |
| uiMsgSize = (IMG_UINT32)((size_t)pNext - (size_t)ppHdr); |
| |
| pSentinel->uiMsgLen += uiMsgSize; |
| |
| pData = GET_PACKET_DATA_PTR(ppHdr); |
| |
| if (pData == NULL || pData >= pLast) |
| { |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: pData = %p, pLast = %p Returning 0", |
| __func__, pData, pLast)); |
| #endif /* HTB_CHATTY */ |
| return 0; |
| } |
| |
| ui32Data = *(IMG_UINT32 *)pData; |
| ui32LogIdx = idToLogIdx(ui32Data); |
| |
| /* |
| * Check if the unrecognised ID is valid and therefore, tracebuf |
| * needs updating. |
| */ |
| if (ui32LogIdx == HTB_SF_LAST) |
| { |
| if (HTB_LOG_VALIDID(ui32Data)) |
| { |
| if (!bUnrecognizedErrorPrinted) |
| { |
| PVR_DPF((PVR_DBG_WARNING, |
| "%s: Unrecognised LOG value '%x' GID %x Params %d ID %x @ '%p'", |
| __func__, ui32Data, HTB_SF_GID(ui32Data), |
| HTB_SF_PARAMNUM(ui32Data), ui32Data & 0xfff, pData)); |
| bUnrecognizedErrorPrinted = IMG_TRUE; |
| } |
| |
| return 0; |
| } |
| |
| PVR_DPF((PVR_DBG_ERROR, |
| "%s: Unrecognised and invalid LOG value detected '%x'", |
| __func__, ui32Data)); |
| |
| return -1; |
| } |
| |
| /* The string format we are going to display */ |
| /* |
| * The display will show the header (log-ID, group-ID, number of params) |
| * The maximum parameter list length = 15 (only 4bits used to encode) |
| * so we need HEADER + 15 * sizeof(UINT32) and the displayed string |
| * describing the event. We use a buffer in the per-process pSentinel |
| * structure to hold the data. |
| */ |
| pszFmt = aLogs[ui32LogIdx].pszFmt; |
| |
| /* add the message payload size to the running count */ |
| ui32ArgsCur = HTB_SF_PARAMNUM(ui32Data); |
| |
| /* Determine if we've over-filled the buffer and had to drop packets */ |
| bPacketsDropped = CHECK_PACKETS_DROPPED(ppHdr); |
| if (bPacketsDropped || |
| (uiHdrType == PVRSRVTL_PACKETTYPE_MOST_RECENT_WRITE_FAILED)) |
| { |
| /* Flag this as it is useful to know ... */ |
| |
| PVR_DUMPDEBUG_LOG("\n<========================== *** PACKETS DROPPED *** ======================>\n"); |
| } |
| |
| { |
| IMG_UINT32 ui32Timestampns, ui32PID; |
| IMG_UINT64 ui64Timestamp, ui64TimestampSec; |
| IMG_CHAR *szBuffer = pSentinel->szBuffer; // Buffer start |
| IMG_CHAR *pszBuffer = pSentinel->szBuffer; // Current place in buf |
| size_t uBufBytesAvailable = sizeof(pSentinel->szBuffer); |
| IMG_UINT32 *pui32Data = (IMG_UINT32 *)pData; |
| IMG_UINT32 ui_aGroupIdx; |
| |
| // Get PID field from data stream |
| pui32Data++; |
| ui32PID = *pui32Data; |
| // Get Timestamp part 1 from data stream |
| pui32Data++; |
| ui64Timestamp = (IMG_UINT64) *pui32Data << 32; |
| // Get Timestamp part 2 from data stream |
| pui32Data++; |
| ui64Timestamp |= (IMG_UINT64) *pui32Data; |
| // Move to start of message contents data |
| pui32Data++; |
| |
| /* |
| * We need to snprintf the data to a local in-kernel buffer |
| * and then PVR_DUMPDEBUG_LOG() that in one shot |
| */ |
| ui_aGroupIdx = MIN(HTB_SF_GID(ui32Data), uiMax_aGroups); |
| |
| /* Divide by 1B to get seconds & mod using output var (nanosecond resolution)*/ |
| ui64TimestampSec = OSDivide64r64(ui64Timestamp, 1000000000, &ui32Timestampns); |
| |
| nPrinted = OSSNPrintf(szBuffer, uBufBytesAvailable, "%010"IMG_UINT64_FMTSPEC".%09u:%5u-%s> ", |
| ui64TimestampSec, ui32Timestampns, ui32PID, aGroups[ui_aGroupIdx]); |
| if (nPrinted >= uBufBytesAvailable) |
| { |
| PVR_DUMPDEBUG_LOG("Buffer overrun - "IMG_SIZE_FMTSPEC" printed," |
| " max space "IMG_SIZE_FMTSPEC"\n", nPrinted, |
| uBufBytesAvailable); |
| |
| nPrinted = uBufBytesAvailable; /* Ensure we don't overflow buffer */ |
| } |
| |
| PVR_DUMPDEBUG_LOG("%s", pszBuffer); |
| /* Update where our next 'output' point in the buffer is */ |
| pszBuffer += nPrinted; |
| uBufBytesAvailable -= nPrinted; |
| |
| /* |
| * Print one argument at a time as this simplifies handling variable |
| * number of arguments. Special case handling for no arguments. |
| * This is the case for simple format strings such as |
| * HTB_SF_MAIN_KICK_UNCOUNTED. |
| */ |
| if (ui32ArgsCur == 0) |
| { |
| if (pszFmt) |
| { |
| nPrinted = OSStringLCopy(pszBuffer, pszFmt, uBufBytesAvailable); |
| if (nPrinted >= uBufBytesAvailable) |
| { |
| PVR_DUMPDEBUG_LOG("Buffer overrun - "IMG_SIZE_FMTSPEC" printed," |
| " max space "IMG_SIZE_FMTSPEC"\n", nPrinted, |
| uBufBytesAvailable); |
| nPrinted = uBufBytesAvailable; /* Ensure we don't overflow buffer */ |
| } |
| PVR_DUMPDEBUG_LOG("%s", pszBuffer); |
| pszBuffer += nPrinted; |
| /* Don't update the uBufBytesAvailable as we have finished this |
| * message decode. pszBuffer - szBuffer is the total amount of |
| * data we have decoded. |
| */ |
| } |
| } |
| else |
| { |
| if (HTB_SF_GID(ui32Data) == HTB_GID_CTRL && HTB_SF_ID(ui32Data) == HTB_ID_MARK_SCALE) |
| { |
| IMG_UINT32 i; |
| IMG_UINT32 ui32ArgArray[HTB_MARK_SCALE_ARG_ARRAY_SIZE]; |
| IMG_UINT64 ui64OSTS = 0; |
| IMG_UINT32 ui32OSTSRem = 0; |
| IMG_UINT64 ui64CRTS = 0; |
| |
| /* Retrieve 6 args to an array */ |
| for (i = 0; i < ARRAY_SIZE(ui32ArgArray); i++) |
| { |
| ui32ArgArray[i] = *pui32Data; |
| pui32Data++; |
| --ui32ArgsCur; |
| } |
| |
| ui64OSTS = (IMG_UINT64) ui32ArgArray[HTB_ARG_OSTS_PT1] << 32 | ui32ArgArray[HTB_ARG_OSTS_PT2]; |
| ui64CRTS = (IMG_UINT64) ui32ArgArray[HTB_ARG_CRTS_PT1] << 32 | ui32ArgArray[HTB_ARG_CRTS_PT2]; |
| |
| /* Divide by 1B to get seconds, remainder in nano seconds*/ |
| ui64OSTS = OSDivide64r64(ui64OSTS, 1000000000, &ui32OSTSRem); |
| |
| nPrinted = OSSNPrintf(pszBuffer, |
| uBufBytesAvailable, |
| "HTBFWMkSync Mark=%u OSTS=%010" IMG_UINT64_FMTSPEC ".%09u CRTS=%" IMG_UINT64_FMTSPEC " CalcClkSpd=%u \n", |
| ui32ArgArray[HTB_ARG_SYNCMARK], |
| ui64OSTS, |
| ui32OSTSRem, |
| ui64CRTS, |
| ui32ArgArray[HTB_ARG_CLKSPD]); |
| |
| if (nPrinted >= uBufBytesAvailable) |
| { |
| PVR_DUMPDEBUG_LOG("Buffer overrun - "IMG_SIZE_FMTSPEC" printed," |
| " max space "IMG_SIZE_FMTSPEC"\n", nPrinted, |
| uBufBytesAvailable); |
| nPrinted = uBufBytesAvailable; /* Ensure we don't overflow buffer */ |
| } |
| |
| PVR_DUMPDEBUG_LOG("%s", pszBuffer); |
| pszBuffer += nPrinted; |
| uBufBytesAvailable -= nPrinted; |
| } |
| else |
| { |
| while (IS_VALID_FMT_STRING(pszFmt) && (uBufBytesAvailable > 0)) |
| { |
| IMG_UINT32 ui32TmpArg = *pui32Data; |
| TRACEBUF_ARG_TYPE eArgType; |
| |
| eArgType = ExtractOneArgFmt(&pszFmt, aszOneArgFmt); |
| |
| pui32Data++; |
| ui32ArgsCur--; |
| |
| switch (eArgType) |
| { |
| case TRACEBUF_ARG_TYPE_INT: |
| nPrinted = OSSNPrintf(pszBuffer, uBufBytesAvailable, |
| aszOneArgFmt, ui32TmpArg); |
| break; |
| |
| case TRACEBUF_ARG_TYPE_NONE: |
| nPrinted = OSStringLCopy(pszBuffer, pszFmt, |
| uBufBytesAvailable); |
| break; |
| |
| default: |
| nPrinted = OSSNPrintf(pszBuffer, uBufBytesAvailable, |
| "Error processing arguments, type not " |
| "recognized (fmt: %s)", aszOneArgFmt); |
| break; |
| } |
| if (nPrinted >= uBufBytesAvailable) |
| { |
| PVR_DUMPDEBUG_LOG("Buffer overrun - "IMG_SIZE_FMTSPEC" printed," |
| " max space "IMG_SIZE_FMTSPEC"\n", nPrinted, |
| uBufBytesAvailable); |
| nPrinted = uBufBytesAvailable; /* Ensure we don't overflow buffer */ |
| } |
| PVR_DUMPDEBUG_LOG("%s", pszBuffer); |
| pszBuffer += nPrinted; |
| uBufBytesAvailable -= nPrinted; |
| } |
| /* Display any remaining text in pszFmt string */ |
| if (pszFmt) |
| { |
| nPrinted = OSStringLCopy(pszBuffer, pszFmt, uBufBytesAvailable); |
| if (nPrinted >= uBufBytesAvailable) |
| { |
| PVR_DUMPDEBUG_LOG("Buffer overrun - "IMG_SIZE_FMTSPEC" printed," |
| " max space "IMG_SIZE_FMTSPEC"\n", nPrinted, |
| uBufBytesAvailable); |
| nPrinted = uBufBytesAvailable; /* Ensure we don't overflow buffer */ |
| } |
| PVR_DUMPDEBUG_LOG("%s", pszBuffer); |
| pszBuffer += nPrinted; |
| /* Don't update the uBufBytesAvailable as we have finished this |
| * message decode. pszBuffer - szBuffer is the total amount of |
| * data we have decoded. |
| */ |
| } |
| } |
| } |
| |
| /* Update total bytes processed */ |
| pSentinel->uiTotal += (pszBuffer - szBuffer); |
| } |
| return 0; |
| } |
| |
| /* |
| * HTBDumpBuffer: Dump the Host Trace Buffer using the TLClient API |
| * |
| * This routine just parses *one* message from the buffer. |
| * The stream will be opened by the Start() routine, closed by the Stop() and |
| * updated for data consumed by this routine once we have DebugPrintf'd it. |
| * We use the new TLReleaseDataLess() routine which enables us to update the |
| * HTB contents with just the amount of data we have successfully processed. |
| * If we need to leave the data available we can call this with a 0 count. |
| * This will happen in the case of a buffer overflow so that we can reprocess |
| * any data which wasn't handled before. |
| * |
| * In case of overflow or an error we return -1 otherwise 0 |
| * |
| * Input: |
| * pfnDumpDebugPrintf output routine to display data |
| * pvDumpDebugFile seq_file handle (from kernel seq_read() call) |
| * pvData data address to start dumping from |
| * (set by Start() / Next()) |
| */ |
| static int HTBDumpBuffer(DUMPDEBUG_PRINTF_FUNC *pfnDumpDebugPrintf, |
| void *pvDumpDebugFile, |
| void *pvData) |
| { |
| struct seq_file *psSeqFile = (struct seq_file *)pvDumpDebugFile; |
| HTB_Sentinel_t *pSentinel = (HTB_Sentinel_t *)psSeqFile->private; |
| |
| PVR_ASSERT(NULL != pvData); |
| |
| if (pvData == SEQ_START_TOKEN) |
| { |
| if (pSentinel->pCurr == NULL) |
| { |
| #ifdef HTB_CHATTY |
| PVR_DPF((PVR_DBG_WARNING, "%s: SEQ_START_TOKEN, Empty buffer", |
| __func__)); |
| #endif /* HTB_CHATTY */ |
| return 0; |
| } |
| PVR_ASSERT(pSentinel->pCurr != NULL); |
| |
| /* Display a Header as we have data to process */ |
| seq_printf(psSeqFile, "%-20s:%-5s-%s %s\n", |
| "Timestamp", "PID", "Group>", "Log Entry"); |
| } |
| else |
| { |
| if (pvData != NULL) |
| { |
| PVR_ASSERT(pSentinel->pCurr == pvData); |
| } |
| } |
| |
| return DecodeHTB(pSentinel, pvDumpDebugFile, pfnDumpDebugPrintf); |
| } |
| |
| |
| /****************************************************************************** |
| * External Entry Point routines ... |
| *****************************************************************************/ |
| /*************************************************************************/ /*! |
| @Function HTB_CreateFSEntry |
| |
| @Description Create the debugFS entry-point for the host-trace-buffer |
| |
| @Returns eError internal error code, PVRSRV_OK on success |
| |
| */ /*************************************************************************/ |
| PVRSRV_ERROR HTB_CreateFSEntry(void) |
| { |
| PVRSRV_ERROR eError; |
| |
| eError = PVRDebugFSCreateFile("host_trace", NULL, |
| &gsHTBReadOps, |
| NULL, NULL, NULL, |
| &g_sHTBData.psDumpHostDebugFSEntry); |
| |
| PVR_LOGR_IF_ERROR(eError, "PVRDebugFSCreateEntry"); |
| |
| return eError; |
| } |
| |
| |
| /*************************************************************************/ /*! |
| @Function HTB_DestroyFSEntry |
| |
| @Description Destroy the debugFS entry-point created by earlier |
| HTB_CreateFSEntry() call. |
| */ /**************************************************************************/ |
| void HTB_DestroyFSEntry(void) |
| { |
| if (g_sHTBData.psDumpHostDebugFSEntry) |
| { |
| PVRDebugFSRemoveFile(&g_sHTBData.psDumpHostDebugFSEntry); |
| } |
| } |
| |
| /* EOF */ |