/**************************************************************************** * * Copyright (C) 2005 - 2011 by Vivante Corp. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the license, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****************************************************************************/ #include "gc_hal_kernel_precomp.h" #if gcdENABLE_VG /******************************************************************************\ *********************** Support Functions and Definitions ********************** \******************************************************************************/ /* Interruot statistics will be accumulated if not zero. */ #define gcmENABLE_INTERRUPT_STATISTICS 0 #define _GC_OBJ_ZONE gcvZONE_INTERRUPT /* Object structure. */ struct _gckVGINTERRUPT { /* Object. */ gcsOBJECT object; /* gckVGKERNEL pointer. */ gckVGKERNEL kernel; /* gckOS pointer. */ gckOS os; /* Interrupt handlers. */ gctINTERRUPT_HANDLER handlers[32]; /* Main interrupt handler thread. */ gctTHREAD handler; gctBOOL terminate; /* Interrupt FIFO. */ gctSEMAPHORE fifoValid; gctUINT32 fifo[256]; gctUINT fifoItems; gctUINT8 head; gctUINT8 tail; /* Interrupt statistics. */ #if gcmENABLE_INTERRUPT_STATISTICS gctUINT maxFifoItems; gctUINT fifoOverflow; gctUINT maxSimultaneous; gctUINT multipleCount; #endif }; /******************************************************************************* ** ** _ProcessInterrupt ** ** The interrupt processor. ** ** INPUT: ** ** ThreadParameter ** Pointer to the gckVGINTERRUPT object. ** ** OUTPUT: ** ** Nothing. */ #if gcmENABLE_INTERRUPT_STATISTICS static void _ProcessInterrupt( gckVGINTERRUPT Interrupt, gctUINT_PTR TriggeredCount ) #else static void _ProcessInterrupt( gckVGINTERRUPT Interrupt ) #endif { gceSTATUS status; gctUINT32 triggered; gctUINT i; /* Advance to the next entry. */ Interrupt->tail += 1; Interrupt->fifoItems -= 1; /* Get the interrupt value. */ triggered = Interrupt->fifo[Interrupt->tail]; gcmkASSERT(triggered != 0); gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, "%s: triggered=0x%08X\n", __FUNCTION__, triggered ); /* Walk through all possible interrupts. */ for (i = 0; i < gcmSIZEOF(Interrupt->handlers); i += 1) { /* Test if interrupt happened. */ if ((triggered & 1) == 1) { #if gcmENABLE_INTERRUPT_STATISTICS if (TriggeredCount != gcvNULL) { (* TriggeredCount) += 1; } #endif /* Make sure we have valid handler. */ if (Interrupt->handlers[i] == gcvNULL) { gcmkTRACE( gcvLEVEL_ERROR, "%s: Interrupt %d isn't registered.\n", __FUNCTION__, i ); } else { gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, "%s: interrupt=%d\n", __FUNCTION__, i ); /* Call the handler. */ status = Interrupt->handlers[i] (Interrupt->kernel); if (gcmkIS_ERROR(status)) { /* Failed to signal the semaphore. */ gcmkTRACE( gcvLEVEL_ERROR, "%s: Error %d incrementing the semaphore #%d.\n", __FUNCTION__, status, i ); } } } /* Next interrupt. */ triggered >>= 1; /* No more interrupts to handle? */ if (triggered == 0) { break; } } } /******************************************************************************* ** ** _MainInterruptHandler ** ** The main interrupt thread serves the interrupt FIFO and calls registered ** handlers for the interrupts that occured. The handlers are called in the ** sequence interrupts occured with the exception when multiple interrupts ** occured at the same time. In that case the handler calls are "sorted" by ** the interrupt number therefore giving the interrupts with lower numbers ** higher priority. ** ** INPUT: ** ** ThreadParameter ** Pointer to the gckVGINTERRUPT object. ** ** OUTPUT: ** ** Nothing. */ static gctTHREADFUNCRESULT gctTHREADFUNCTYPE _MainInterruptHandler( gctTHREADFUNCPARAMETER ThreadParameter ) { gceSTATUS status; gckVGINTERRUPT interrupt; #if gcmENABLE_INTERRUPT_STATISTICS gctUINT count; #endif /* Cast the object. */ interrupt = (gckVGINTERRUPT) ThreadParameter; /* Enter the loop. */ while (gcvTRUE) { /* Wait for an interrupt. */ status = gckOS_DecrementSemaphore(interrupt->os, interrupt->fifoValid); /* Error? */ if (gcmkIS_ERROR(status)) { break; } /* System termination request? */ if (status == gcvSTATUS_TERMINATE) { break; } /* Driver is shutting down? */ if (interrupt->terminate) { break; } #if gcmENABLE_INTERRUPT_STATISTICS /* Reset triggered count. */ count = 0; /* Process the interrupt. */ _ProcessInterrupt(interrupt, &count); /* Update conters. */ if (count > interrupt->maxSimultaneous) { interrupt->maxSimultaneous = count; } if (count > 1) { interrupt->multipleCount += 1; } #else /* Process the interrupt. */ _ProcessInterrupt(interrupt); #endif } return 0; } /******************************************************************************* ** ** _StartInterruptHandler / _StopInterruptHandler ** ** Main interrupt handler routine control. ** ** INPUT: ** ** ThreadParameter ** Pointer to the gckVGINTERRUPT object. ** ** OUTPUT: ** ** Nothing. */ static gceSTATUS _StartInterruptHandler( gckVGINTERRUPT Interrupt ) { gceSTATUS status, last; do { /* Objects must not be already created. */ gcmkASSERT(Interrupt->fifoValid == gcvNULL); gcmkASSERT(Interrupt->handler == gcvNULL); /* Reset the termination request. */ Interrupt->terminate = gcvFALSE; #if !gcdENABLE_INFINITE_SPEED_HW /* Construct the fifo semaphore. */ gcmkERR_BREAK(gckOS_CreateSemaphoreVG( Interrupt->os, &Interrupt->fifoValid )); /* Start the interrupt handler thread. */ gcmkERR_BREAK(gckOS_StartThread( Interrupt->os, _MainInterruptHandler, Interrupt, &Interrupt->handler )); #endif /* Success. */ return gcvSTATUS_OK; } while (gcvFALSE); /* Roll back. */ if (Interrupt->fifoValid != gcvNULL) { gcmkCHECK_STATUS(gckOS_DestroySemaphore( Interrupt->os, Interrupt->fifoValid )); Interrupt->fifoValid = gcvNULL; } /* Return the status. */ return status; } static gceSTATUS _StopInterruptHandler( gckVGINTERRUPT Interrupt ) { gceSTATUS status; do { /* Does the thread exist? */ if (Interrupt->handler == gcvNULL) { /* The semaphore must be NULL as well. */ gcmkASSERT(Interrupt->fifoValid == gcvNULL); /* Success. */ status = gcvSTATUS_OK; break; } /* The semaphore must exist as well. */ gcmkASSERT(Interrupt->fifoValid != gcvNULL); /* Set the termination request. */ Interrupt->terminate = gcvTRUE; /* Unlock the thread. */ gcmkERR_BREAK(gckOS_IncrementSemaphore( Interrupt->os, Interrupt->fifoValid )); /* Wait until the thread quits. */ gcmkERR_BREAK(gckOS_StopThread( Interrupt->os, Interrupt->handler )); /* Destroy the semaphore. */ gcmkERR_BREAK(gckOS_DestroySemaphore( Interrupt->os, Interrupt->fifoValid )); /* Reset handles. */ Interrupt->handler = gcvNULL; Interrupt->fifoValid = gcvNULL; } while (gcvFALSE); /* Return the status. */ return status; } /******************************************************************************\ ***************************** Interrupt Object API ***************************** \******************************************************************************/ /******************************************************************************* ** ** gckVGINTERRUPT_Construct ** ** Construct an interrupt object. ** ** INPUT: ** ** Kernel ** Pointer to the gckVGKERNEL object. ** ** OUTPUT: ** ** Interrupt ** Pointer to the new gckVGINTERRUPT object. */ gceSTATUS gckVGINTERRUPT_Construct( IN gckVGKERNEL Kernel, OUT gckVGINTERRUPT * Interrupt ) { gceSTATUS status; gckVGINTERRUPT interrupt = gcvNULL; gcmkHEADER_ARG("Kernel=0x%x Interrupt=0x%x", Kernel, Interrupt); /* Verify argeuments. */ gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL); gcmkVERIFY_ARGUMENT(Interrupt != gcvNULL); do { /* Allocate the gckVGINTERRUPT structure. */ gcmkERR_BREAK(gckOS_Allocate( Kernel->os, gcmSIZEOF(struct _gckVGINTERRUPT), (gctPOINTER *) &interrupt )); /* Reset the object data. */ gcmkVERIFY_OK(gckOS_ZeroMemory( interrupt, gcmSIZEOF(struct _gckVGINTERRUPT) )); /* Initialize the object. */ interrupt->object.type = gcvOBJ_INTERRUPT; /* Initialize the object pointers. */ interrupt->kernel = Kernel; interrupt->os = Kernel->os; /* Initialize the current FIFO position. */ interrupt->head = (gctUINT8)~0; interrupt->tail = (gctUINT8)~0; /* Start the thread. */ gcmkERR_BREAK(_StartInterruptHandler(interrupt)); /* Return interrupt object. */ *Interrupt = interrupt; /* Success. */ return gcvSTATUS_OK; } while (gcvFALSE); /* Roll back. */ if (interrupt != gcvNULL) { /* Free the gckVGINTERRUPT structure. */ gcmkVERIFY_OK(gckOS_Free(interrupt->os, interrupt)); } /* Return the status. */ return status; } /******************************************************************************* ** ** gckVGINTERRUPT_Destroy ** ** Destroy an interrupt object. ** ** INPUT: ** ** Interrupt ** Pointer to the gckVGINTERRUPT object to destroy. ** ** OUTPUT: ** ** Nothing. */ gceSTATUS gckVGINTERRUPT_Destroy( IN gckVGINTERRUPT Interrupt ) { gceSTATUS status; gcmkHEADER_ARG("Interrupt=0x%x", Interrupt); /* Verify the arguments. */ gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT); do { /* Stop the interrupt thread. */ gcmkERR_BREAK(_StopInterruptHandler(Interrupt)); /* Mark the object as unknown. */ Interrupt->object.type = gcvOBJ_UNKNOWN; /* Free the gckVGINTERRUPT structure. */ gcmkERR_BREAK(gckOS_Free(Interrupt->os, Interrupt)); } while (gcvFALSE); gcmkFOOTER(); /* Return the status. */ return status; } /******************************************************************************* ** ** gckVGINTERRUPT_DumpState ** ** Print the current state of the interrupt manager. ** ** INPUT: ** ** Interrupt ** Pointer to a gckVGINTERRUPT object. ** ** OUTPUT: ** ** Nothing. */ #if gcvDEBUG gceSTATUS gckVGINTERRUPT_DumpState( IN gckVGINTERRUPT Interrupt ) { gcmkHEADER_ARG("Interrupt=0x%x", Interrupt); /* Verify the arguments. */ gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT); /* Print the header. */ gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, "%s: INTERRUPT OBJECT STATUS\n", __FUNCTION__ ); /* Print statistics. */ #if gcmENABLE_INTERRUPT_STATISTICS gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, " Maximum number of FIFO items accumulated at a single time: %d\n", Interrupt->maxFifoItems ); gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, " Interrupt FIFO overflow happened times: %d\n", Interrupt->fifoOverflow ); gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, " Maximum number of interrupts simultaneously generated: %d\n", Interrupt->maxSimultaneous ); gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, " Number of times when there were multiple interrupts generated: %d\n", Interrupt->multipleCount ); #endif gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, " The current number of entries in the FIFO: %d\n", Interrupt->fifoItems ); /* Print the FIFO contents. */ if (Interrupt->fifoItems != 0) { gctUINT8 index; gctUINT8 last; gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, " FIFO current contents:\n" ); /* Get the current pointers. */ index = Interrupt->tail; last = Interrupt->head; while (index != last) { /* Advance to the next entry. */ index += 1; gcmkTRACE_ZONE( gcvLEVEL_VERBOSE, gcvZONE_COMMAND, " %d: 0x%08X\n", index, Interrupt->fifo[index] ); } } gcmkFOOTER_NO(); /* Success. */ return gcvSTATUS_OK; } #endif /******************************************************************************* ** ** gckVGINTERRUPT_Enable ** ** Enable the specified interrupt. ** ** INPUT: ** ** Interrupt ** Pointer to a gckVGINTERRUPT object. ** ** Id ** Pointer to the variable that holds the interrupt number to be ** registered in range 0..31. ** If the value is less then 0, gckVGINTERRUPT_Enable will attempt ** to find an unused interrupt. If such interrupt is found, the number ** will be assigned to the variable if the functuion call succeedes. ** ** Handler ** Pointer to the handler to register for the interrupt. ** ** OUTPUT: ** ** Nothing. */ gceSTATUS gckVGINTERRUPT_Enable( IN gckVGINTERRUPT Interrupt, IN OUT gctINT32_PTR Id, IN gctINTERRUPT_HANDLER Handler ) { gceSTATUS status; gctINT32 i; gcmkHEADER_ARG("Interrupt=0x%x Id=0x%x Handler=0x%x", Interrupt, Id, Handler); /* Verify the arguments. */ gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT); gcmkVERIFY_ARGUMENT(Id != gcvNULL); gcmkVERIFY_ARGUMENT(Handler != gcvNULL); do { /* See if we need to allocate an ID. */ if (*Id < 0) { /* Find the first unused interrupt handler. */ for (i = 0; i < gcmCOUNTOF(Interrupt->handlers); ++i) { if (Interrupt->handlers[i] == gcvNULL) { break; } } /* No unused innterrupts? */ if (i == gcmCOUNTOF(Interrupt->handlers)) { status = gcvSTATUS_OUT_OF_RESOURCES; break; } /* Update the interrupt ID. */ *Id = i; } /* Make sure the ID is in range. */ else if (*Id >= gcmCOUNTOF(Interrupt->handlers)) { status = gcvSTATUS_INVALID_ARGUMENT; break; } /* Set interrupt handler. */ Interrupt->handlers[*Id] = Handler; /* Success. */ status = gcvSTATUS_OK; } while (gcvFALSE); gcmkFOOTER(); /* Return the status. */ return status; } /******************************************************************************* ** ** gckVGINTERRUPT_Disable ** ** Disable the specified interrupt. ** ** INPUT: ** ** Interrupt ** Pointer to a gckVGINTERRUPT object. ** ** Id ** Interrupt number to be disabled in range 0..31. ** ** OUTPUT: ** ** Nothing. */ gceSTATUS gckVGINTERRUPT_Disable( IN gckVGINTERRUPT Interrupt, IN gctINT32 Id ) { gcmkHEADER_ARG("Interrupt=0x%x Id=0x%x", Interrupt, Id); /* Verify the arguments. */ gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT); gcmkVERIFY_ARGUMENT((Id >= 0) && (Id < gcmCOUNTOF(Interrupt->handlers))); /* Reset interrupt handler. */ Interrupt->handlers[Id] = gcvNULL; gcmkFOOTER_NO(); /* Success. */ return gcvSTATUS_OK; } /******************************************************************************* ** ** gckVGINTERRUPT_Enque ** ** Read the interrupt status register and put the value in the interrupt FIFO. ** ** INPUT: ** ** Interrupt ** Pointer to a gckVGINTERRUPT object. ** ** OUTPUT: ** ** Nothing. */ gceSTATUS gckVGINTERRUPT_Enque( IN gckVGINTERRUPT Interrupt ) { gceSTATUS status; gctUINT32 triggered; gcmkHEADER_ARG("Interrupt=0x%x", Interrupt); /* Verify the arguments. */ gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT); do { /* Read interrupt status register. */ gcmkERR_BREAK(gckVGHARDWARE_ReadInterrupt( Interrupt->kernel->hardware, &triggered )); /* No interrupts to process? */ if (triggered == 0) { status = gcvSTATUS_NOT_OUR_INTERRUPT; break; } /* FIFO overflow? */ if (Interrupt->fifoItems == gcmCOUNTOF(Interrupt->fifo)) { #if gcmENABLE_INTERRUPT_STATISTICS Interrupt->fifoOverflow += 1; #endif /* OR the interrupt with the last value in the FIFO. */ Interrupt->fifo[Interrupt->head] |= triggered; /* Success (kind of). */ status = gcvSTATUS_OK; } else { /* Advance to the next entry. */ Interrupt->head += 1; Interrupt->fifoItems += 1; #if gcmENABLE_INTERRUPT_STATISTICS if (Interrupt->fifoItems > Interrupt->maxFifoItems) { Interrupt->maxFifoItems = Interrupt->fifoItems; } #endif /* Set the new value. */ Interrupt->fifo[Interrupt->head] = triggered; /* Increment the FIFO semaphore. */ gcmkERR_BREAK(gckOS_IncrementSemaphore( Interrupt->os, Interrupt->fifoValid )); /* Windows kills our threads prematurely when the application exists. Verify here that the thread is still alive. */ status = gckOS_VerifyThread(Interrupt->os, Interrupt->handler); /* Has the thread been prematurely terminated? */ if (status != gcvSTATUS_OK) { /* Process all accumulated interrupts. */ while (Interrupt->head != Interrupt->tail) { #if gcmENABLE_INTERRUPT_STATISTICS /* Process the interrupt. */ _ProcessInterrupt(Interrupt, gcvNULL); #else /* Process the interrupt. */ _ProcessInterrupt(Interrupt); #endif } /* Set success. */ status = gcvSTATUS_OK; } } } while (gcvFALSE); gcmkFOOTER(); /* Return status. */ return status; } #endif /* gcdENABLE_VG */