os/kernelhwsrv/kernel/eka/nkern/nk_timer.cpp
author sl@SLION-WIN7.fritz.box
Fri, 15 Jun 2012 03:10:57 +0200
changeset 0 bde4ae8d615e
permissions -rw-r--r--
First public contribution.
     1 // Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
     2 // All rights reserved.
     3 // This component and the accompanying materials are made available
     4 // under the terms of the License "Eclipse Public License v1.0"
     5 // which accompanies this distribution, and is available
     6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
     7 //
     8 // Initial Contributors:
     9 // Nokia Corporation - initial contribution.
    10 //
    11 // Contributors:
    12 //
    13 // Description:
    14 // e32\nkern\nk_timer.cpp
    15 // Fast Millisecond Timer Implementation
    16 // This file is just a template - you'd be mad not to machine code this
    17 // 
    18 //
    19 
    20 #include "nk_priv.h"
    21 
    22 const TInt KTimerQDfcPriority=6;
    23 
    24 GLDEF_D NTimerQ TheTimerQ;
    25 
    26 #ifndef __MSTIM_MACHINE_CODED__
    27 #ifdef _DEBUG
    28 #define __DEBUG_CALLBACK(n)	{if (iDebugFn) (*iDebugFn)(iDebugPtr,n);}
    29 #else
    30 #define __DEBUG_CALLBACK(n)
    31 #endif
    32 
    33 
    34 /** Starts a nanokernel timer in one-shot mode with ISR callback.
    35 	
    36 	Queues the timer to expire in the specified number of nanokernel ticks. The
    37 	actual wait time will be at least that much and may be up to one tick more.
    38 	The expiry handler will be called in ISR context.
    39 	
    40 	Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
    41 
    42 	@param	aTime Timeout in nanokernel ticks
    43 	
    44 	@return	KErrNone if no error; KErrInUse if timer is already active.
    45 	
    46 	@pre	Any context
    47 	
    48 	@see    NKern::TimerTicks()
    49  */
    50 EXPORT_C TInt NTimer::OneShot(TInt aTime)
    51 	{
    52 	return OneShot(aTime,FALSE);
    53 	}
    54 
    55 
    56 /** Starts a nanokernel timer in one-shot mode with ISR or DFC callback.
    57 	
    58 	Queues the timer to expire in the specified number of nanokernel ticks. The
    59 	actual wait time will be at least that much and may be up to one tick more.
    60 	The expiry handler will be called in either ISR context or in the context
    61 	of the nanokernel timer thread (DfcThread1).
    62 
    63     Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
    64 
    65 	@param	aTime Timeout in nanokernel ticks
    66 	@param	aDfc TRUE if DFC callback required, FALSE if ISR callback required.
    67 	
    68 	@return	KErrNone if no error; KErrInUse if timer is already active.
    69 	
    70 	@pre	Any context
    71 	
    72 	@see    NKern::TimerTicks()
    73  */
    74 EXPORT_C TInt NTimer::OneShot(TInt aTime, TBool aDfc)
    75 	{
    76 	__NK_ASSERT_DEBUG(aTime>=0);
    77 
    78 	/** iFunction could be set to NULL after NTimer::OneShot(TInt, TDfc&) call.
    79 	Call-back mechanism cannot be changed in the life time of a timer. */
    80 	__NK_ASSERT_DEBUG(iFunction!=NULL); 
    81 
    82 	TInt irq=NKern::DisableAllInterrupts();
    83 	if (iState!=EIdle)
    84 		{
    85 		NKern::RestoreInterrupts(irq);
    86 		return KErrInUse;
    87 		}
    88 	iCompleteInDfc=TUint8(aDfc?1:0);
    89 	iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime;
    90 	TheTimerQ.Add(this);
    91 	NKern::RestoreInterrupts(irq);
    92 	return KErrNone;
    93 	}
    94 
    95 /** Starts a nanokernel timer in one-shot mode with callback in dfc thread that provided DFC belongs to.
    96 	
    97 	Queues the timer to expire in the specified number of nanokernel ticks. The
    98 	actual wait time will be at least that much and may be up to one tick more.
    99 	On expiry aDfc will be queued in ISR context.
   100 
   101     Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
   102 
   103 	@param	aTime Timeout in nanokernel ticks
   104 	@param	aDfc - Dfc to be queued when the timer expires.
   105 	
   106 	@return	KErrNone if no error; KErrInUse if timer is already active.
   107 	
   108 	@pre	Any context
   109 	
   110 	@see    NKern::TimerTicks()
   111  */
   112 EXPORT_C TInt NTimer::OneShot(TInt aTime, TDfc& aDfc)
   113 	{
   114 	__NK_ASSERT_DEBUG(aTime>=0);
   115 	TInt irq=NKern::DisableAllInterrupts();
   116 	if (iState!=EIdle)
   117 		{
   118 		NKern::RestoreInterrupts(irq);
   119 		return KErrInUse;
   120 		}
   121 	iCompleteInDfc = 0;
   122 	iFunction = NULL;
   123 	iPtr = (TAny*) &aDfc;
   124 	iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime;
   125 	TheTimerQ.Add(this);
   126 	NKern::RestoreInterrupts(irq);
   127 	return KErrNone;
   128 	}
   129 
   130 
   131 /** Starts a nanokernel timer in zero-drift periodic mode with ISR or DFC callback.
   132 
   133 	Queues the timer to expire in the specified number of nanokernel ticks,
   134 	measured from the time at which it last expired. This allows exact periodic
   135 	timers to be implemented with no drift caused by delays in requeueing the
   136 	timer.
   137 
   138 	The expiry handler will be called in the same context as the previous timer
   139 	expiry. Generally the way this is used is that NTimer::OneShot() is used to start 
   140 	the first time interval and this specifies whether the callback is in ISR context 
   141 	or in the context of the nanokernel timer thread (DfcThread1) or other Dfc thread.
   142 	The expiry handler then uses NTimer::Again() to requeue the timer.
   143 
   144 	@param	aTime Timeout in nanokernel ticks
   145 
   146 	@return	KErrNone if no error; KErrInUse if timer is already active;
   147 	        KErrArgument if the requested expiry time is in the past.
   148 	        
   149 	@pre	Any context
   150  */
   151 EXPORT_C TInt NTimer::Again(TInt aTime)
   152 //
   153 // Wait aTime from last trigger time - used for periodic timers
   154 //
   155 	{
   156 	__NK_ASSERT_DEBUG(aTime>0);
   157 	TInt irq=NKern::DisableAllInterrupts();
   158 	if (iState!=EIdle)
   159 		{
   160 		NKern::RestoreInterrupts(irq);
   161 		return KErrInUse;
   162 		}
   163 	TUint32 nextTick=TheTimerQ.iMsCount;
   164 	TUint32 trigger=iTriggerTime+(TUint32)aTime;
   165 	TUint32 d=trigger-nextTick;
   166 	if (d>=0x80000000)
   167 		{
   168 		NKern::RestoreInterrupts(irq);
   169 		return KErrArgument;		// requested time is in the past
   170 		}
   171 	iTriggerTime=trigger;
   172 	TheTimerQ.Add(this);
   173 	NKern::RestoreInterrupts(irq);
   174 	return KErrNone;
   175 	}
   176 
   177 
   178 /** Cancels a nanokernel timer.
   179 
   180 	Removes this timer from the nanokernel timer queue. Does nothing if the
   181 	timer is inactive or has already expired.
   182 	Note that if the timer was queued and DFC callback requested it is possible
   183 	for the expiry handler to run even after Cancel() has been called. This will
   184 	occur in the case where DfcThread1 is preempted just before calling the
   185 	expiry handler for this timer and the preempting thread/ISR/IDFC calls
   186 	Cancel() on the timer.
   187 
   188 	@pre	Any context
   189 	@return	TRUE if timer was actually cancelled
   190 	@return	FALSE if timer was not cancelled - this could be because it was not
   191 				active or because its expiry handler was already running on
   192 				another CPU or in the timer DFC.
   193  */
   194 EXPORT_C TBool NTimer::Cancel()
   195 	{
   196 	TBool result = TRUE;
   197 	TInt irq=NKern::DisableAllInterrupts();
   198 	if (iState>ETransferring)	// idle or transferring timers are not on a queue
   199 		Deque();
   200 	switch (iState)
   201 		{
   202 		case ETransferring:	// signal DFC to abort this iteration
   203 			TheTimerQ.iTransferringCancelled=TRUE;
   204 			break;
   205 		case ECritical:		// signal DFC to abort this iteration
   206 			TheTimerQ.iCriticalCancelled=TRUE;
   207 			break;
   208 		case EFinal:
   209 			{
   210 			// Need to clear bit in iPresent if both final queues now empty
   211 			// NOTE: Timer might actually be on the completed queue rather than the final queue
   212 			//		 but the check is harmless in any case.
   213 			TInt i=iTriggerTime & NTimerQ::ETimerQMask;
   214 			NTimerQ::STimerQ& q=TheTimerQ.iTickQ[i];
   215 			if (q.iIntQ.IsEmpty() && q.iDfcQ.IsEmpty())
   216 				TheTimerQ.iPresent &= ~(1<<i);
   217 			break;
   218 			}
   219 		case EIdle:			// nothing to do
   220 			result = FALSE;
   221 		case EHolding:		// just deque
   222 		case EOrdered:		// just deque
   223 			break;
   224 		}
   225 	iState=EIdle;
   226 	NKern::RestoreInterrupts(irq);
   227 	return result;
   228 	}
   229 #endif
   230 
   231 
   232 /** Check if a nanokernel timer is pending or not
   233 
   234 	@return	TRUE if the timer is pending (OneShot() etc. would return KErrInUse)
   235 	@return FALSE if the timer is idle (OneShot() etc. would succeed)
   236 	@pre	Any context
   237 
   238 	@publishedPartner
   239 	@prototype
   240  */
   241 EXPORT_C TBool NTimer::IsPending()
   242 	{
   243 	return iState != EIdle;
   244 	}
   245 
   246 
   247 /** Obtains the address of the nanokernel timer queue object.
   248 
   249 	Not intended for general use. Intended only for base ports in order to get
   250 	the address used to call NTimerQ::Tick() with.
   251 
   252 	@return	The address of the nanokernel timer queue object
   253 	@pre	Any context
   254  */
   255 EXPORT_C TAny* NTimerQ::TimerAddress()
   256 	{
   257 	return &TheTimerQ;
   258 	}
   259 
   260 NTimerQ::NTimerQ()
   261 	:	iDfc(NTimerQ::DfcFn,this,NULL,KTimerQDfcPriority)
   262 	{
   263 	// NOTE: All other members are initialised to zero since the single instance
   264 	//		 of NTimerQ resides in .bss
   265 	}
   266 
   267 void NTimerQ::Init1(TInt aTickPeriod)
   268 	{
   269 	TheTimerQ.iTickPeriod=aTickPeriod;
   270 	__KTRACE_OPT(KBOOT,DEBUGPRINT("NTimerQ::Init1 - period %d us",aTickPeriod));
   271 	__KTRACE_OPT(KMEMTRACE, DEBUGPRINT("MT:P %d",aTickPeriod));
   272 	}
   273 
   274 void NTimerQ::Init3(TDfcQue* aDfcQ)
   275 	{
   276 	__KTRACE_OPT(KBOOT,DEBUGPRINT("NTimerQ::Init3 DFCQ at %08x",aDfcQ));
   277 	TheTimerQ.iDfc.SetDfcQ(aDfcQ);
   278 	}
   279 
   280 #ifndef __MSTIM_MACHINE_CODED__
   281 void NTimerQ::Add(NTimer* aTimer)
   282 //
   283 //	Internal function to add a timer to the queue.
   284 //	Enter and return with all interrupts disabled.
   285 //
   286 	{
   287 	TInt t=TInt(aTimer->iTriggerTime-iMsCount);
   288 	if (t<ENumTimerQueues)
   289 		AddFinal(aTimer);
   290 	else
   291 		{
   292 		// >=32ms to expiry, so put on holding queue
   293 		aTimer->iState=NTimer::EHolding;
   294 		iHoldingQ.Add(aTimer);
   295 		}
   296 	}
   297 
   298 void NTimerQ::AddFinal(NTimer* aTimer)
   299 //
   300 //	Internal function to add a timer to the corresponding final queue.
   301 //	Enter and return with all interrupts disabled.
   302 //
   303 	{
   304 	TInt i=aTimer->iTriggerTime & ETimerQMask;
   305 	SDblQue* pQ;
   306 	if (aTimer->iCompleteInDfc)
   307 		pQ=&iTickQ[i].iDfcQ;
   308 	else
   309 		pQ=&iTickQ[i].iIntQ;
   310 	iPresent |= (1<<i);
   311 	aTimer->iState=NTimer::EFinal;
   312 	pQ->Add(aTimer);
   313 	}
   314 
   315 void NTimerQ::DfcFn(TAny* aPtr)
   316 	{
   317 	((NTimerQ*)aPtr)->Dfc();
   318 	}
   319 
   320 void NTimerQ::Dfc()
   321 //
   322 // Do deferred timer queue processing and/or DFC completions
   323 //
   324 	{
   325 	TInt irq;
   326 
   327 	// First transfer entries on the Ordered queue to the Final queues
   328 	FOREVER
   329 		{
   330 		irq=NKern::DisableAllInterrupts();
   331 		if (iOrderedQ.IsEmpty())
   332 			break;
   333 		NTimer* pC=(NTimer*)iOrderedQ.First();
   334 		TInt remain=pC->iTriggerTime-iMsCount;
   335 		if (remain>=ENumTimerQueues)
   336 			break;
   337 
   338 		// If remaining time <32 ticks, add it to final queue;
   339 		// also if remain < 0 we've 'missed it' so add to final queue.
   340 		pC->Deque();
   341 		AddFinal(pC);
   342 		NKern::RestoreInterrupts(irq);
   343 		__DEBUG_CALLBACK(0);
   344 		}
   345 	NKern::RestoreInterrupts(irq);
   346 	__DEBUG_CALLBACK(1);
   347 
   348 	// Next transfer entries on the Holding queue to the Ordered queue or final queue
   349 	FOREVER
   350 		{
   351 		irq=NKern::DisableAllInterrupts();
   352 		if (iHoldingQ.IsEmpty())
   353 			break;
   354 		NTimer* pC=(NTimer*)iHoldingQ.First();
   355 		pC->Deque();
   356 		pC->iState=NTimer::ETransferring;
   357 		iTransferringCancelled=FALSE;
   358 		TUint32 trigger=pC->iTriggerTime;
   359 		if (TInt(trigger-iMsCount)<ENumTimerQueues)
   360 			{
   361 			// <32ms remaining so put it on final queue
   362 			AddFinal(pC);
   363 			}
   364 		else
   365 			{
   366 			FOREVER
   367 				{
   368 				NKern::RestoreInterrupts(irq);
   369 				__DEBUG_CALLBACK(2);
   370 
   371 				// we now need to walk ordered queue to find correct position for pC
   372 				SDblQueLink* anchor=&iOrderedQ.iA;
   373 				iCriticalCancelled=FALSE;
   374 				irq=NKern::DisableAllInterrupts();
   375 				NTimer* pN=(NTimer*)iOrderedQ.First();
   376 				while (pN!=anchor && !iTransferringCancelled)
   377 					{
   378 					if ((pN->iTriggerTime-trigger)<0x80000000u)
   379 						break;	// insert before pN
   380 					pN->iState=NTimer::ECritical;
   381 					NKern::RestoreInterrupts(irq);
   382 					__DEBUG_CALLBACK(3);
   383 					irq=NKern::DisableAllInterrupts();
   384 					if (iCriticalCancelled)
   385 						break;
   386 					pN->iState=NTimer::EOrdered;
   387 					pN=(NTimer*)pN->iNext;
   388 					}
   389 
   390 				if (iTransferringCancelled)
   391 					break;		// this one has been cancelled, go on to next one
   392 				if (!iCriticalCancelled)
   393 					{
   394 					pC->InsertBefore(pN);
   395 					pC->iState=NTimer::EOrdered;
   396 					break;		// done this one
   397 					}
   398 				}
   399 			}
   400 		NKern::RestoreInterrupts(irq);
   401 		__DEBUG_CALLBACK(4);
   402 		}
   403 	NKern::RestoreInterrupts(irq);
   404 	__DEBUG_CALLBACK(5);
   405 
   406 	// Finally do call backs for timers which requested DFC callback
   407 	FOREVER
   408 		{
   409 		irq=NKern::DisableAllInterrupts();
   410 		if (iCompletedQ.IsEmpty())
   411 			break;
   412 		NTimer* pC=(NTimer*)iCompletedQ.First();
   413 		pC->Deque();
   414 		pC->iState=NTimer::EIdle;
   415 		TAny* p=pC->iPtr;
   416 		NTimerFn f=pC->iFunction;
   417 		NKern::RestoreInterrupts(irq);
   418 		__DEBUG_CALLBACK(7);
   419 		(*f)(p);
   420 		}
   421 	NKern::RestoreInterrupts(irq);
   422 	}
   423 
   424 
   425 /** Tick over the nanokernel timer queue.
   426 	This function should be called by the base port in the system tick timer ISR.
   427 	It should not be called at any other time.
   428 	The value of 'this' to pass is the value returned by NTimerQ::TimerAddress().
   429 
   430 	@see NTimerQ::TimerAddress()
   431  */
   432 EXPORT_C void NTimerQ::Tick()
   433 	{
   434 #ifdef _DEBUG
   435 	// If there are threads waiting to be released by the tick, enqueue the dfc
   436 	if (!TheScheduler.iDelayedQ.IsEmpty())
   437 		TheScheduler.iDelayDfc.Add();
   438 #endif
   439 	TheScheduler.TimesliceTick();
   440 	TInt irq=NKern::DisableAllInterrupts();
   441 	TInt i=iMsCount & ETimerQMask;
   442 	iMsCount++;
   443 	STimerQ* pQ=iTickQ+i;
   444 	iPresent &= ~(1<<i);
   445 	TBool doDfc=FALSE;
   446 	if (!pQ->iDfcQ.IsEmpty())
   447 		{
   448 		// transfer DFC completions to completed queue and queue DFC
   449 		iCompletedQ.MoveFrom(&pQ->iDfcQ);
   450 		doDfc=TRUE;
   451 		}
   452 	if ((i&(ETimerQMask>>1))==0)
   453 		{
   454 		// Every 16 ticks we check if a DFC is required.
   455 		// This allows a DFC latency of up to 16 ticks before timers are missed.
   456 		if (!iHoldingQ.IsEmpty())
   457 			doDfc=TRUE;				// if holding queue nonempty, queue DFC to sort
   458 		else if (!iOrderedQ.IsEmpty())
   459 			{
   460 			// if first ordered queue entry expires in <32ms, queue the DFC to transfer
   461 			NTimer* pC=(NTimer*)iOrderedQ.First();
   462 #ifdef __EPOC32__
   463 			__ASSERT_WITH_MESSAGE_DEBUG(iMsCount<=pC->iTriggerTime, "iMsCount has exceeded pC->iTriggerTime; function called later than expected ","NTimerQ::Tick()");
   464 #endif
   465 			if (TInt(pC->iTriggerTime-iMsCount)<ENumTimerQueues)
   466 				doDfc=TRUE;
   467 			}
   468 		}
   469 	if (!pQ->iIntQ.IsEmpty())
   470 		{
   471 		// transfer ISR completions to a temporary queue
   472 		// careful here - higher priority interrupts could dequeue timers!
   473 		SDblQue q(&pQ->iIntQ,0);
   474 		while(!q.IsEmpty())
   475 			{
   476 			NTimer* pC=(NTimer*)q.First();
   477 			pC->Deque();
   478 			pC->iState=NTimer::EIdle;
   479 			NKern::RestoreInterrupts(irq);
   480 			if (pC->iFunction)
   481 				(*pC->iFunction)(pC->iPtr);
   482 			else
   483 				((TDfc*)(pC->iPtr))->Add();
   484 			irq=NKern::DisableAllInterrupts();
   485 			}
   486 		}
   487 	NKern::RestoreInterrupts(irq);
   488 	if (doDfc)
   489 		iDfc.Add();
   490 	}
   491 
   492 
   493 /** Return the number of ticks before the next nanokernel timer expiry.
   494 	May on occasion return a pessimistic estimate (i.e. too low).
   495 	Used by base port to disable the system tick interrupt when the system
   496 	is idle.
   497 
   498 	@return	The number of ticks before the next nanokernel timer expiry.
   499 	
   500 	@pre	Interrupts must be disabled.
   501 	
   502 	@post	Interrupts are disabled.
   503  */
   504 EXPORT_C TInt NTimerQ::IdleTime()
   505 	{
   506 	CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED,"NTimerQ::IdleTime");	
   507 #ifdef _DEBUG
   508 	// If there are threads waiting to be released by the tick we can't idle
   509 	if (!TheScheduler.iDelayedQ.IsEmpty())
   510 		return 1;
   511 #endif
   512 	NTimerQ& m=TheTimerQ;
   513 	TUint32 next=m.iMsCount;	// number of next tick
   514 	TUint32 p=m.iPresent;
   515 	TInt r=KMaxTInt;
   516 	if (p)
   517 		{
   518 		// Final queues nonempty
   519 		TInt nx=next&0x1f;				// number of next tick modulo 32
   520 		p=(p>>nx)|(p<<(32-nx));			// rotate p right by nx (so lsb corresponds to next tick)
   521 		r=__e32_find_ls1_32(p);			// find number of zeros before LS 1
   522 		}
   523 	if (!m.iHoldingQ.IsEmpty())
   524 		{
   525 		// Sort operation required - need to process next tick divisible by 16
   526 		TInt nx=next&0x0f;				// number of next tick modulo 16
   527 		TInt r2=nx?(16-nx):0;			// number of ticks before next divisible by 16
   528 		if (r2<r)
   529 			r=r2;
   530 		}
   531 	if (!m.iOrderedQ.IsEmpty())
   532 		{
   533 		// Timers present on ordered queue
   534 		NTimer* pC=(NTimer*)m.iOrderedQ.First();
   535 		TUint32 tt=pC->iTriggerTime;
   536 		tt=(tt&~0x0f)-16;				// time at which transfer to final queue would occur
   537 		TInt r3=(TInt)(tt-next);
   538 		if (r3<r)
   539 			r=r3;
   540 		}
   541 	return r;
   542 	}
   543 #endif
   544 
   545 
   546 /** Advance the nanokernel timer queue by the specified number of ticks.
   547 	It is assumed that no timers expire as a result of this.
   548 	Used by base port when system comes out of idle mode after disabling the
   549 	system tick interrupt to bring the timer queue up to date.
   550 
   551 	@param	aTicks Number of ticks skipped due to tick suppression
   552 
   553 	@pre	Interrupts must be disabled.
   554 
   555 	@post	Interrupts are disabled.
   556  */
   557 EXPORT_C void NTimerQ::Advance(TInt aTicks)
   558 	{
   559 	CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED,"NTimerQ::Advance");	
   560 	TheTimerQ.iMsCount+=(TUint32)aTicks;
   561 	}
   562 
   563 
   564 /**	Returns the period of the nanokernel timer.
   565 	@return Period in microseconds
   566 	@pre any context
   567 	@see NTimer
   568  */
   569 EXPORT_C TInt NKern::TickPeriod()
   570 	{
   571 	return TheTimerQ.iTickPeriod;
   572 	}
   573 
   574 
   575 /**	Converts a time interval to timer ticks.
   576 
   577 	@param aMilliseconds time interval in milliseconds.
   578 	@return Number of nanokernel timer ticks.  Non-integral results are rounded up.
   579 
   580  	@pre aMilliseconds should be <=2147483 to avoid integer overflow.
   581 	@pre any context
   582  */
   583 EXPORT_C TInt NKern::TimerTicks(TInt aMilliseconds)
   584 	{
   585 	__ASSERT_WITH_MESSAGE_DEBUG(aMilliseconds<=2147483,"aMilliseconds should be <=2147483","NKern::TimerTicks");
   586 	TUint32 msp=TheTimerQ.iTickPeriod;
   587 	if (msp==1000)	// will be true except on pathological hardware
   588 		return aMilliseconds;
   589 	TUint32 us=(TUint32)aMilliseconds*1000;
   590 	return (us+msp-1)/msp;
   591 	}
   592