/****************************************************************************/
/* Copyright 2003 MBARI														*/
/****************************************************************************/
/* Summary	: Driver Management Routines for OASIS5 Mooring Controller		*/
/* Filename : drvr.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS Mooring Replacement (OASIS5)							*/
/* Revision : 1.0															*/
/* Created	: 07/19/2016													*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/****************************************************************************/
/* Modification History:													*/
/* 07jan2003 rah - OASIS3 version created from OASIS drvr.c					*/
/* 19jul2016 rah - OASIS5 version created from OASIS3 drvr.c				*/
/* $Log$
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <custom.h>						/* Custom def's for deployment		*/
#include <debug.h>
#include <dig_io.h>						/* OASIS5 digital I/O				*/
#include <drvr.h>						/* OASIS driver functions			*/
#include <serial.h>						/* OASIS Serial I/O definitions		*/
#include <olist.h>						/* OASIS linked list definitions	*/
#include <malloc.h>						/* OASIS malloc routines			*/
#include <log.h>						/* Log record definitions			*/
#include <otask.h>						/* OASIS Multitasking definitions	*/
#include <utils.h>						/* OASIS utility functions			*/
#include <config.h>						/* OASIS Config file parser			*/
//#include <modbus.h>					/* Modbus protocol library			*/
#include <remote.h>						/* Remote I/O library				*/

#include <string.h>						/* String library					*/

#include <FreeRTOS.h>					/* FreeRTOS definitions				*/
#include <semphr.h>						/* FreeRTOS semaphore functions		*/


/********************************/
/*		External Data			*/
/********************************/

Extern Nat32			pwr_perm;		/* Devices to leave powered on		*/
Extern time_t			radio_time;		/* Last time user connected on radio*/
										/*	(set in userif.c, used here)	*/
Extern Nat32			wakeOnConsoleChar;
Extern Nat32			serPortsWoke;
Extern Parm_t			sync_tmout;		/* Timeout for drvSync				*/
Extern MBool			syncAtMidnight;
Extern MBool			deploymentMode;
Extern MBool			allowDrvrConflict;


/********************************/
/*		Global Data				*/
/********************************/

Global LstHead			drv_list;				/* Driver list header		*/


/************************************************************************/
/* Function	   : nullDrvrFunc											*/
/* Purpose	   : Null function											*/
/* Inputs	   : Driver *												*/
/* Outputs	   : None													*/
/************************************************************************/
void nullDrvrFunc(Driver *dp)
{
} /* nullDrvrFunc() */


/************************************************************************/
/* Function	   : nullWakeFunc											*/
/* Purpose	   : Null function											*/
/* Inputs	   : Driver *, bool to wakeup/sleep, buffer ptr				*/
/* Outputs	   : TRUE													*/
/************************************************************************/
MBool nullWakeFunc(Driver *dp, MBool wakeup, Byte *buffer)
{
	return(TRUE);

} /* nullWakeFunc() */


/************************************************************************/
/* Function	   : nullLsampleFunc										*/
/* Purpose	   : Null function											*/
/* Inputs	   : Driver *												*/
/* Outputs	   : None													*/
/************************************************************************/
CmdRtn nullLsampleFunc(Driver *dp)
{
	return(OK);

} /* nullLsampleFunc() */


/************************************************************************/
/* Function	   : pwrBitOn												*/
/* Purpose	   : Turn on a (physical or virtual) power bit				*/
/* Inputs	   : Power bit number										*/
/* Outputs	   : OK or ERROR number										*/
/* Comment     : Power and Serial controlled from same relay on			*/
/*               MotherBoard and DaughterBoard, but different on expander*/
/************************************************************************/
Errno pwrBitOn(Nat32 pwrnum)
{
	if (isPhysPwr(pwrnum))
	{ 
		digPwrOn(pwrnum);
		return(OK);
	}
	else		/* Virtual power port */
		return(remPower(pwrnum, TRUE));

} /* pwrBitOn() */


/************************************************************************/
/* Function	   : pwrBitOff												*/
/* Purpose	   : Turn off a (physical or virtual) power bit				*/
/* Inputs	   : Power bit number										*/
/* Outputs	   : OK or ERROR number										*/
/* Comment     : Power and Serial controlled from same relay on			*/
/*               MotherBoard and DaughterBoard, but different on expander*/
/************************************************************************/
Errno pwrBitOff(Nat32 pwrnum)
{
	if (pwr_perm & (1L<<pwrnum))		/* If perm pwred, don't turn off*/
		return(OK);

	if (isPhysPwr(pwrnum))
	{
		digPwrOff(pwrnum);
		return(OK);
	}
	else								/* Virtual power port 			*/
		return(remPower(pwrnum, FALSE));

} /* pwrBitOff() */


/************************************************************************/
/* Function	   : drvPwrVecSet											*/
/* Purpose	   : Set power vector, including remote						*/
/* Inputs	   : Power Vector											*/
/* Outputs	   : OK or ERROR number										*/
/* Comment     : Power and Serial controlled from same relay on			*/
/*               MotherBoard and DaughterBoard, but different on expander*/
/************************************************************************/
void drvPwrVecSet(Nat32 pwrVec)
{
	Nat32	i, vec;

	digPwrVecSet(pwrVec & ((1<<PIC_UARTS) - 1));

	vec = pwrVec >> PIC_UARTS;;

	for (i = PIC_UARTS; i < 32; i++, vec >>= 1)
		remPower(i, vec & 1);

} /* drvPwrVecSec() */


/************************************************************************/
/* Function	   : drv_pwron												*/
/* Purpose	   : Turn on power for given driver							*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/* Comment     : Power and Serial controlled from same relay on			*/
/*               MotherBoard and DaughterBoard, but different on expander*/
/************************************************************************/
void drv_pwron(Driver *dp)
{
	pwrBitOn(dp->drv_parms[SER_PORT]);

} /* drv_pwron() */


/************************************************************************/
/* Function	   : drv_pwroff												*/
/* Purpose	   : Turn off power for given driver						*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/* Comment     : Power and Serial controlled from same relay on			*/
/*               MotherBoard and DaughterBoard, but different on expander*/
/************************************************************************/
void drv_pwroff(Driver *dp)
{
	if ((dp->drv_parms[FLAGS] & PWRPERM) == 0)
		pwrBitOff(dp->drv_parms[SER_PORT]);

} /* drv_pwroff() */


/************************************************************************/
/* Function	   : drv_ser_port											*/
/* Purpose	   : Get access to serial port, init port					*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
void drv_ser_port(Driver *dp)
{
	Reg Port_t	virtPort;

	if ((virtPort = dp->drv_parms[SER_PORT]) == NO_SERIAL)
		return;

	if (isPhysSer(virtPort))
	{
		getSerialPort(virtPort);			/* Get serial sem			*/
		pwrBitOn(virtPort);					/* Enables serial relay		*/
		digSerEnable(virtPort);				/* Enables serial Tx		*/
		serPortInit(virtPort, dp->drv_parms[SER_BAUD], dp->drv_parms[SER_SETUP]);
	}
	else
	{
		if (remOpenVirtSer(virtPort, FALSE) != OK)
			return;

		remVirtSerInit(virtPort, dp->drv_parms[SER_BAUD],
					   dp->drv_parms[SER_SETUP]);

		drv_pwron(dp);						/* Turn on device				*/
	}
	
} /* drv_ser_port() */


/************************************************************************/
/* Function	   : drv_ser_release										*/
/* Purpose	   : Release access to serial port							*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
void drv_ser_release(Driver *dp)
{
	Reg Port_t	virtPort;

	if ((virtPort = dp->drv_parms[SER_PORT]) == NO_SERIAL)
		return;
	
	if (isPhysSer(virtPort))				/* If physical ser port	  */
	{
		xdrain_ser(400);					/* Wait for output to drain*/
#ifdef DEBUG_DRVR_TIMES
		if (dp->drv_parms[SER_PORT] != SER_CONSOLE)
#endif
			digSerDisable(virtPort);		/* Disable the EIA driver  */
		releaseSerialPort(virtPort);		/* Release the serial port	*/
	}
	else
		remCloseVirtSer(virtPort);

} /* drv_ser_release() */
						

/************************************************************************/
/* Function	   : drvr_find												*/
/* Purpose	   : Find a driver by name									*/
/* Inputs	   : Pointer to name string									*/
/* Outputs	   : Corresponding driver, or DRV_NULL if none				*/
/************************************************************************/
Driver *drvr_find(char *name)
{
	Reg Driver	*dp;

	lockList(&drv_list);

	for ( dp = (Driver *)(drv_list.lst_head); dp != DRV_NULL; dp = dp->drv_next )
		if ( cmp_ulc(name, dp->drv_name) != NULL )
		{
			unlockList(&drv_list);
			return(dp);
		}

	unlockList(&drv_list);
	return(DRV_NULL);

} /* drvr_find() */


/************************************************************************/
/* Function	   : drvrChkName											*/
/* Purpose	   : Check validity of a driver name, fix if illegal chars	*/
/* Inputs	   : Name pointer											*/
/* Outputs	   : OK or ERROR											*/
/* Comments	   : Driver name becomes Instrument name in instr log		*/
/************************************************************************/
MLocal Errno drvrChkName(char *name)
{
	char		*p;
	Nat16		i;
	Errno		rtn = OK;

	for (i = 0, p = name; i < strlen(name); i++, p++)
		if (!(isalnum(*p) || (*p == '-') || (*p == '_') || (*p == '.')))
		{
			*p = '_';
			rtn = ERROR;
		}

	return(rtn);
}


/************************************************************************/
/* Function	   : drvr_create											*/
/* Purpose	   : Create a driver header									*/
/* Inputs	   : Ptr to driver descriptor								*/
/* Outputs	   : Driver pointer											*/
/* Comments	   : Adds it to the system driver list						*/
/************************************************************************/
Driver *drvr_create(const DrvDesc *ddp)
{
	Reg Driver	*dp;
	Reg Nat16	namelen;
	Reg Nat32	sermask;

	namelen = strlen(ddp->dd_name) + 1;

	if ( (dp = (Driver *)malloc(sizeof(Driver) + namelen)) != DRV_NULL )
	{
		memset(dp, 0, sizeof(Driver));
		memcpy(&(dp->drv_name), ddp, sizeof(DrvDesc));
		dp->drv_name = (char *)(dp + 1);
		memcpy(dp->drv_name, ddp->dd_name, namelen);
		drvrChkName(dp->drv_name);
		dp->drv_lastlog = -1;
		dp->drv_wakeup = clkTime();			/* Set to sample now		*/
		dp->drv_runflags = DO_INIT;
		dp->drv_td = NULLTID;				/* Init no driver tasks		*/

		sermask = (dp->drv_parms[SER_PORT] != NO_SERIAL) ?
			(1L << dp->drv_parms[SER_PORT]) : 0;

		if ((dp->drv_parms[FLAGS] & PWRPERM) && sermask)
		{
			pwr_perm |= sermask;
			drv_pwron(dp);
		}

		if ((dp->drv_parms[FLAGS] & CONSOLE_MODE) && sermask)
			wakeOnConsoleChar |= sermask;

		list_add(&drv_list, (Node *)dp);
	}
	return(dp);

} /* drvr_create() */


/************************************************************************/
/* Function	   : checkDrvrConflicts										*/
/* Purpose	   : Look for conflicting declarations of serial, power ports*/
/*				 Warn user if any found									*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void checkDrvrConflicts(void)
{
	Reg Driver	*dp, *dp2;
	Port_t		serport;

	lockList(&drv_list);

	for (dp = (Driver *)drv_list.lst_head; dp != DRV_NULL; dp = dp->drv_next)
	{
		serport = dp->drv_parms[SER_PORT];
		if (!allowDrvrConflict)
		{
			for (dp2 = dp->drv_next; dp2 != DRV_NULL; dp2 = dp2->drv_next)
			{
				if ((serport != NO_SERIAL) && (serport == dp2->drv_parms[SER_PORT]))
					xprintf("WARNING!  Drivers %s and %s have conflict on serial port %lu\n",
							dp->drv_name, dp2->drv_name, serport);
			}
		}

		if (modBusConfigured(serport))
			xprintf("WARNING!  Driver %s has conflict with modbus port %u\n",
					dp->drv_name, serport);
  }

	unlockList(&drv_list);
	newline();

} /* checkDrvrConflicts() */


/************************************************************************/
/* Function	   : drvr_init												*/
/* Purpose	   : Initialize driver module								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void drvr_init(void)
{
	list_init(&drv_list);				/* Init driver list				*/
	wakeOnConsoleChar = 0;

	if ((parseConfigFile(CFG_FILE) != OK) || (list_count(&drv_list) == 0))
	{										/* Parse cfg file.	If bad,		*/
		list_init(&drv_list);				/*	add default drivers			*/
		parseAddDefaultDrivers();
	}

	checkDrvrConflicts();					/* Look for driver conflicts	*/

} /* drvr_init() */


/************************************************************************/
/* Function	   : drvWakeupTime											*/
/* Purpose	   : Determine time until driver needs to wake up			*/
/* Inputs	   : Driver pointer											*/
/* Outputs	   : Seconds until driver wakeup, or 0 if wakeup now		*/
/************************************************************************/
MLocal Nat32 drvWakeupTime(Reg Driver *dp)
{
	Reg Nat32		serport;
	Reg Nat16		nexthr;
	time_t			tod = clkTime();

	serport = dp->drv_parms[SER_PORT];

	if ((serport != NO_SERIAL) &&
		(dp->drv_parms[FLAGS] & CONSOLE_MODE) &&
		(serRxCount(serport) || (serPortsWoke & (1<<serport))))
		return(0);

	if (tod < dp->drv_wakeup)					/* If not time to run,	*/
		return(dp->drv_wakeup - tod);			/* return time to wakeup*/

										/* Time to wakeup now. Check flags*/
	if (dp->drv_runflags & DO_SYNC)				/* If SYNC flag on, run */
		return(0);								/*	 it now				*/

	if (dp->drv_parms[INTERVAL] == 0)			/* If driver turned off */
		return(INT32_MAX);						/*	 return Max Int32	*/

	if (!deploymentMode &&						/* Check TOD mask		*/
		 (dp->drv_parms[TOD_MASK] & ((unsigned long)1 << clkLocalHour())))
	{								/* If TOD mask on for this 1 hr period, */
									/*	   go to next hour					*/
		nexthr = 3600 - (tod % 3600);
		dp->drv_wakeup = tod + nexthr;
		return(nexthr);
	}

	return(0);									/* If none above, run now*/

} /* drvWakeupTime() */


/************************************************************************/
/* Function	   : drvrWrapper											*/
/* Purpose	   : Wrapper for driver task								*/
/* Inputs	   : Driver pointer											*/
/* Outputs	   : None, never returns (task destroyed first)				*/
/************************************************************************/
MLocal void drvrWrapper(void *arg)
{
	Driver	*dp = (Driver *)arg;

	dp->drv_serdesc.serport = dp->drv_parms[SER_PORT];
	dp->drv_serdesc.sermode = dp->drv_parms[SER_SETUP];
	dp->drv_serdesc.lastChar = '\0';

	vTaskSetThreadLocalStoragePointer(NULL, ConsoleP, &dp->drv_serdesc);
	vTaskSetThreadLocalStoragePointer(NULL, DrvrP, dp);
	vTaskSetThreadLocalStoragePointer(NULL, State, (void *)READY);
	vTaskSetThreadLocalStoragePointer(NULL, StateParm, NULL);
	vTaskSetThreadLocalStoragePointer(NULL, Malloc1, NULL);
	vTaskSetThreadLocalStoragePointer(NULL, Malloc2, NULL);
	dp->drv_td = xTaskGetCurrentTaskHandle();

	(*dp->drv_task)(dp);					/* Call the driver sample func*/

	remTaskExit(dp->drv_td);				/* Release remote resources	*/
	semTaskExit(dp->drv_td);				/* Release any mutex sems	*/
	mallocTaskExit(dp->drv_td);				/* Release any malloc'd mem	*/
	dp->drv_td = NULLTID;					/* Show driver not running	*/
	if ( (dp->drv_runflags & FLAG_PERM) == 0) /* If PERM flag not set,	*/
		dp->drv_runflags = 0;				/*  turn off temp flags		*/
	vTaskDelete(NULL);						/* Kill task, never returns	*/
}


/************************************************************************/
/* Function	   : drvr_sample											*/
/* Purpose	   : (Re)start any driver not already running that should be*/
/* Inputs	   : None													*/
/* Outputs	   : Seconds until next driver wakeup						*/
/* Comments	   : Will restart any driver not already running that has	*/
/*				 exipired its wakeup time.								*/
/*				 Returns 0 if any tasks still running, to keep main alive*/
/************************************************************************/
Nat32 drvr_sample(void)
{
	Reg Driver	*dp;					/* Cur drvr ptr					  */
	Reg Nat32	wtime, t;				/* Earliest wake time			  */
	time_t		tod = clkTime();

	if (syncAtMidnight)
		wtime = SECS_PER_DAY - (tod % SECS_PER_DAY);
	else
		wtime = INT32_MAX;

	lockList(&drv_list);

	for ( dp = (Driver *)drv_list.lst_head; dp != DRV_NULL; dp = dp->drv_next )
	{
		if ( dp->drv_td != NULLTID )			/* If anybody running,	   */
			wtime = 0;							/*	 next wake is now	   */
		else
		{
			if ((t = drvWakeupTime(dp)) == 0)	/* If time to wake up	   */
			{									/*	start the driver	   */

				if (xTaskCreate(drvrWrapper, dp->drv_name, DRV_STACK_WORDS,
								dp, DRVR_PRIO, &dp->drv_td) == pdPASS)

				{				/* Normal case is to sample at constant rate*/
					dp->drv_wakeup += dp->drv_parms[INTERVAL];

					/* If we've been off more than one interval,	   */
					/*	 restart timing from now					   */
					if (dp->drv_wakeup <= tod)
						dp->drv_wakeup = tod + dp->drv_parms[INTERVAL];
				}
				else			/* If task_create fails, try again in 2 sec*/
					dp->drv_wakeup = tod + 2;

#ifdef DEBUG_DRVR_TIMES
				xprintf("Sampling %s  ", dp->drv_name);
				clkPrintTime(&tod);
				xprintf("  ");
				clkPrintTime(&dp->drv_wakeup);
				xprintf("  tick = %u\n", xTaskGetTickCount());
#endif
			}

			if ( t < wtime )				/* Save min wake time	   */
				wtime = t;
		}
	}

	unlockList(&drv_list);
	return( wtime );

} /* drvr_sample() */


/************************************************************************/
/* Function	   : drvLogError											*/
/* Purpose	   : Log an error for a driver								*/
/* Inputs	   : Driver ptr												*/
/* Outputs	   : None													*/
/************************************************************************/
void drvLogError( Driver *dp, Errno err )
{
	LogRecHdr		rechdr;				/* Logging record header		*/
	Nat16			recbuff[2];

	strcpy(rechdr.log_instrumentName, "ERROR");
	rechdr.log_type = (Byte)LOG_BIN;
	rechdr.log_len = sizeof(recbuff);
	recbuff[0] = swapWord((Nat16)dp->drv_parms[SAMPLE_TYPE]);
	recbuff[1] = swapWord(err);
	logPutRec( &rechdr, (Byte *)recbuff );

} /* drvLogError() */


/************************************************************************/
/* Function	   : drvLog													*/
/* Purpose	   : Default sample logging routine							*/
/* Inputs	   : Driver ptr, sample ptr, number bytes					*/
/* Outputs	   : None													*/
/************************************************************************/
void drvLog(Driver *dp, Byte *samplep, Int16 len)
{
	LogRecHdr		rh;

	strncpy(rh.log_instrumentName, dp->drv_name, sizeof(rh.log_instrumentName));
	rh.log_type = (Nat16)dp->drv_parms[SAMPLE_TYPE];

#ifdef DEBUG_LOG
	dprintf("drvLog, name = %s\r\n", rh.log_instrumentName);
#endif
	if ( (rh.log_len = len) > 0 )
		logPutRec( &rh, (Byte *)samplep );
	else
		drvLogError( dp, NO_DATA );

} /* drvLog() */


/************************************************************************/
/* Function	   : drvLogWords											*/
/* Purpose	   : Default sample logging routine							*/
/* Inputs	   : Driver ptr, sample ptr, number words					*/
/* Outputs	   : None													*/
/************************************************************************/
void drvLogWords(Driver *dp, Nat16 *samplep, Int16 wrds)
{
	Reg Int16		i;

	for (i = 0; i < wrds; i++)
		samplep[i] = swapWord(samplep[i]);

	drvLog( dp, (Byte *)samplep, wrds * sizeof(Nat16) );

} /* drvLogWords() */


/************************************************************************/
/* Function	   : drvSerPortAndMalloc									*/
/* Purpose	   : Get serial port and malloc buffer						*/
/* Inputs	   : Driver Pointer, buffer size							*/
/* Outputs	   : Buffer ptr, or NULL if malloc fails					*/
/************************************************************************/
char *drvSerPortAndMalloc(Driver *dp, size_t size)
{
	char	*mallocPtr;

	drv_ser_port(dp);

	if ( (mallocPtr = malloc(size)) == NULL )
	{
		drv_ser_release( dp );
		drvLogError( dp, MALLOC_ERR );
	}

	return( mallocPtr );

} /* drvSerPortAndMalloc() */


/************************************************************************/
/* Function	   : drvSerReleaseAndFree									*/
/* Purpose	   : Release serial port and malloc buffer					*/
/* Inputs	   : Driver Pointer, buffer ptr								*/
/* Outputs	   : None													*/
/************************************************************************/
void drvSerReleaseAndFree(Driver *dp, char *buffer)
{
	drv_ser_release(dp);
	free(buffer);
	
} /* drvSerReleaseAndFree() */


/************************************************************************/
/* Function	   : drvSync												*/
/* Purpose	   : Sync to an External driver								*/
/* Inputs	   : Driver name											*/
/* Outputs	   : None													*/
/************************************************************************/
void drvSync(char *name)
{
	Driver		*dp;
	Nat16		cnt;

	if ((name == NULL) || ((dp = drvr_find(name)) == DRV_NULL))
		return;

	dp->drv_wakeup = clkTime();
	dp->drv_runflags |= DO_SYNC;
	
	for (cnt = sync_tmout;
		  (cnt > 0) && (dp->drv_runflags & DO_SYNC); cnt--)
		task_delay(TICKS_PER_SEC);

} /* drvSync() */


/************************************************************************/
/* Function	   : drvr_timechange										*/
/* Purpose	   : Notify driver module that new time has been set		*/
/* Inputs	   : Amount that time changed								*/
/* Outputs	   : None													*/
/************************************************************************/
void drvr_timechange(Int32 diff)
{
	Reg Driver	*dp;					/* Driver pointer				*/

	lockList(&drv_list);
	for (dp = (Driver *)drv_list.lst_head; dp != DRV_NULL; dp = dp->drv_next)
		dp->drv_wakeup += diff;

	radio_time += diff;
	unlockList(&drv_list);

} /* drvr_timechange() */


/************************************************************************/
/* Function	   : drvMidnightSync										*/
/* Purpose	   : Do midnight synchronization of drivers					*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void drvMidnightSync(void)
{
	Reg Driver	*dp;					/* Driver ptr					   */
	static Byte	didSync = TRUE;			/* Did midnight sync				*/

	if ( clkLocalHour() != 0 )			/* See if we just passed midnight  */
		didSync = FALSE;				/* If no, reset didSync var		   */
	else if ( !didSync )				/* If yes, did we already sync?	   */
	{
		didSync = TRUE;					/* Do just once per day			   */
		deploymentMode = FALSE;			/* Turn off deployment mode		   */

		lockList(&drv_list);

		for (dp = (Driver *)drv_list.lst_head; dp != DRV_NULL;
			 dp = dp->drv_next)
			if ( dp->drv_parms[INTERVAL] )
			{
				dp->drv_wakeup = clkTime();
				dp->drv_runflags |= DO_SYNC;
			}

		unlockList(&drv_list);
	}
} /* midnightSync() */
