blob: 9389f61068d3dad1a2c58d9df8dcfadb743b19d6 [file] [log] [blame]
/****************************************************************************
*
* The MIT License (MIT)
*
* Copyright (c) 2014 - 2016 Vivante Corporation
*
* 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.
*
* 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. 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.
*
*****************************************************************************
*
* The GPL License (GPL)
*
* Copyright (C) 2014 - 2016 Vivante Corporation
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*****************************************************************************
*
* Note: This software is released under dual MIT and GPL licenses. A
* recipient may use this file under the terms of either the MIT license or
* GPL License. If you wish to use only one license not the other, you can
* indicate your decision by deleting one of the above license notices in your
* version of this file.
*
*****************************************************************************/
#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;
gcmkFOOTER_ARG("*Interrup=0x%x", *Interrupt);
/* Success. */
return gcvSTATUS_OK;
}
while (gcvFALSE);
/* Roll back. */
if (interrupt != gcvNULL)
{
/* Free the gckVGINTERRUPT structure. */
gcmkVERIFY_OK(gckOS_Free(interrupt->os, interrupt));
}
gcmkFOOTER();
/* 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.
*/
#ifndef __QNXNTO__
gceSTATUS
gckVGINTERRUPT_Enque(
IN gckVGINTERRUPT Interrupt
)
#else
gceSTATUS
gckVGINTERRUPT_Enque(
IN gckVGINTERRUPT Interrupt,
OUT gckOS *Os,
OUT gctSEMAPHORE *Semaphore
)
#endif
{
gceSTATUS status;
gctUINT32 triggered;
gcmkHEADER_ARG("Interrupt=0x%x", Interrupt);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT);
#ifdef __QNXNTO__
*Os = gcvNULL;
*Semaphore = gcvNULL;
#endif
do
{
/* Read interrupt status register. */
gcmkERR_BREAK(gckVGHARDWARE_ReadInterrupt(
Interrupt->kernel->hardware, &triggered
));
/* Mask out TS overflow interrupt */
triggered &= 0xfffffffe;
/* 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;
#ifndef __QNXNTO__
/* Increment the FIFO semaphore. */
gcmkERR_BREAK(gckOS_IncrementSemaphore(
Interrupt->os, Interrupt->fifoValid
));
#else
*Os = Interrupt->os;
*Semaphore = Interrupt->fifoValid;
#endif
/* 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 */