aboutsummaryrefslogtreecommitdiff
path: root/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c')
-rw-r--r--drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c854
1 files changed, 854 insertions, 0 deletions
diff --git a/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c b/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c
new file mode 100644
index 00000000000..cbc921a713b
--- /dev/null
+++ b/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c
@@ -0,0 +1,854 @@
+/****************************************************************************
+*
+* 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 */