/*! \file  thread.c
\brief Implantation de la gestion des threads
*/
#include "error.h"
#include "thread.h"
#include "mask.h"
#include "interrupt.h"
#include "countdown.h"
#include "contextswitch.h"
#include "Cstack.h"
#include "mem.h"
#include	<errno.h>

/*! \ingroup IGthread
\brief Descripteur de  threads 

	Le champ FredThreadDesc::err est la sauvegarde de la variable globale errno qui contient le code d'erreur du programme. On la sauve et la restaure à chaque commutation de thread.

*/
typedef struct  FredThreadDesc
{
  FredCELL;   				/*!< pour mettre les thread en file*/
  char * stack;  			/*!<  adr zone pile */
  int      stacksz;		/*!<  taille  de pile  */
  FredRunnable entry; /*!< procédure appelée au lancement du thread*/
  void *      val;    /*!<  argument  */
  void **			resref; /*!< adresse resultat */
	int err;					/*!< sauvegarde code erreur errno*/
  FredContext   ctx  ; /*!< contexte registres processeur */         
} FredThreadDesc;


 
/*! \ingroup IGthread
\brief Compteur du nombre de threads créés non terminés

	Seuls les threads créés par FredThreadSpawn() sont comptés. Ce compteur sert à détecter la terminaison du programme FRED (cf FredWaitTerminate() ).

*/
	int volatile nbFredThreads; /* nombre de threads vivants */

/*! \ingroup IGthread
\brief Le thread actif

*/
 	FredThread   current ;
 
/*! \ingroup IGthread
\brief File d'attente des threads prêts

	Elle n'est jamais vide du fait de l'existence du chien de garde #watchdog
*/
 	FredQueue  readyQ; 


/*! \ingroup IGthread
 \brief File d'attente des threads morts dont la pile est à libérer (réservée à #leon le nettoyeur)
	Un thread terminant s'inscrit dans cette file et réveille #leon s'il n'est pas déja réveillé ( cf leonCode() ).

*/
	FredQueue  deadQ ;
/*! \ingroup IGthread
 \brief  Le nettoyeur leon 

	Comme un thread terminant ne peut libérer la pile dans laquelle il s'exécute, ce travail est assuré par un thread spécifique de nettoyage : #leon . Il attend qu'un thread mourant le réveille dans une file d'attente spécifique #leonQ . Il trouve les threads décédés dans la file #deadQ ( cf leonCode() ) Il assure aussi la détection de terminaison.

*/
	FredThread  leon; 

/*! \ingroup IGthread
 \brief File d'attente  de #leon
*/
	FredQueue  leonQ ; /* file d'attente de leon*/

/*! \ingroup IGthread
 \brief File d'attente de terminaison (réservée au #mainThread )
*/
	FredQueue endQ;   
/*! \brief Le thread associé à la procédure main() 
*/
	FredThread   mainThread;   
/*! \ingroup IGthread
\brief Descripeur du thread associé à la procédure main() 
*/
	FredThreadDesc mainThreadBlock; 

/*! \ingroup IGthread
\brief Thread chien de garde

	Son rôle est d'éviter une file #readyQ  vide. Il ne fait que passer la main. ( cf. watchDogCode() ).
*/
	FredThread  watchdog; 

	






/*! \ingroup IGthread
 \brief  Le code du nettoyeur leon 

	Il est en charge de la libération de mémoire des threads morts et de la détection de terminaison.

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation

leon attend qu'un thread mourant le réveille dans sa file d'attente spécifique #leonQ . Il trouve les threads décédés dans la file #deadQ et libère la mémoire.
Il décrémente le compte de threads #nbFredThreads et détecte la terminaison. Dans ce cas, il réveille le thread principal si nécessaire.

*/

void *  leonCode(void * x)
{
	FredThread t;	
	while(1){
		FredMaskOn();/* début d'exclusion mutuelle */
		/* libérer la mémoire des threads défunts */
		while((t=FredQueueGetFirst(&deadQ))){
			free(t->stack);free(t);
			nbFredThreads--;
		}
		/* si terminaison, on réveille le thread l'attendant */
		if(nbFredThreads==2)FredThreadWakeUp(&endQ);
		/* leon attend de nouveaux décès */
		FredThreadSuspend(&leonQ);
		FredMaskOff();
	}
	/* on ne revient jamais la */
  FredAbort(" erreur fatale dans leon");
}

/*! \ingroup IGthread
 \brief  Initialisation de leon le nettoyeur

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
	On lance leon après avoir initialiser les 2 files #leonQ et #deadQ .

*/


void LeonInit()
{
	FredQueueInit(&deadQ); /*  file vide */
	FredQueueInit(&leonQ); /*  file vide */
	leon=FredThreadSpawn(20000,leonCode,(void*)0,(void**)0);
}
/****************************************************************
*                                                              
* Chien de garde :
* 
*			- S'il n'y a plus de thread, termine le programme
*			- s'il n'y a plus de thread prêts, il tourne.
*				Ce peut être un  deadlock s'il n'y a pas de thread en attente 
*					de signaux externes                                                    
*****************************************************************
*****************************************************************/

/*! \ingroup IGthread
 \brief  Le code du chien de garde watchdog

Il évite d'avoir une file #readyQ vide à traiter.
	
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
	C'est un boucle infinie qui ne fait que passer la main à un autre thread prêt.
*/


void * watchDogCode(void * x)
{
	while(1){
		FredThreadYield();
		/* attraper les interruptions externes */
	}
	/* on ne revient jamais la */
  FredAbort(" erreur fatale dans chien de garde");
}

/*! \ingroup IGthread
 \brief  Initialisation du chien de garde watchdog

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
*/

void WatchDogInit()
{
	watchdog=FredThreadSpawn(20000,watchDogCode,(void*)0,(void**)0);
}

/*! \ingroup IGthread
 \brief  Valeur du quantum en microseconde 
*/
long int quantum; /* nombre de microsecondes */ 


/*! \ingroup IGthread
 \brief  Traitement d'interruption de fin de quantum

	\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
	On recycle en fin de #readyQ le thread courant. On choisit le suivant qu'on relance avec un nouveau décomptage de quantum.

*/
void QuantumHandler(int i)
{
	FredThread old;
  /* interruption de fin de quantum 
		on est masqué contre l'it quantum*/
  /* printf("+"); */
	FredMaskOn(); /* début d'exclusion mutuelle */
	current->err=errno; /* sauver errno */
	old=current;
	FredQueuePutLast(&readyQ,current);
	current=FredQueueGetFirst(&readyQ);
	if(current!=old)FredContextSwitch(old->ctx,current->ctx);
	/* 
		on reviendra ici lors de son réveil :
		on relance le décomptage du quantum
 	*/
	FredCountDownStart(quantum);
	errno=current->err;/* restaurer errno */
	FredMaskOff();/* fin d'exclusion mutuelle */
}

/*! \ingroup IGthread
 \brief  Initialisation du temps partagé 

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
	On initialise le quantum avec une valeur donnée et on attache le traitement d'interruption de fin de quantum au décompteur.

*/

void TimeSharingInit( long int uS)
{
	/* attaché à une interuption un traitant d'IT */
	FredCountDownAttach(QuantumHandler);
	/* valeur du quantum */
	quantum=uS;
}


/***************************************************************
****************************************************************
****************************************************************
*                                                              
*   API threads
*
****************************************************************
*****************************************************************
*****************************************************************/


/***************************************************************/  
/*! \ingroup Gcond
\brief Suspendre le thread courant dans une file
\param q file où mettre le thread courant 

	Le thread courant est mis en queue de la file. Un nouveau thread est élu pour s'exécuter.

\warning Cette procédure n'est utilisable qu'au sein d'une section en exclusion mutuelle. 


\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation

	On doit arrêter puis relancer un décomptage.
*/
void FredThreadSuspend( FredQueue* q)
{
	FredThread old;	
	current->err=errno; /* sauver errno */
	FredCountDownStop(); /* Arrêt décompeur */
	/* le courant se  en queue de la file q */
	FredQueuePutLast(q,current); 
	/* 
		on choisit un nouveau courant : on doit garantir
		que la file readyQ n'est jamais vide : C'est le rôle
		du watchdog
	*/
 	old=current; current=FredQueueGetFirst(&readyQ);
	FredContextSwitch(old->ctx,current->ctx);
	/* 
		on reviendra ici lors de son réveil :
		on relance le décomptage du quantum
	*/
		FredCountDownStart(quantum);
		errno=current->err;/* restaurer errno */
}



/*! \ingroup Gcond
\brief Réveille un  thread bloqué
\param q queue dont- le premier thread est remis prêt

	Le thread réveillé  est mis en queue de la file des prêts. C'est une opération nulle si la file est vide.

\warning Cette procédure n'est utilisable qu'au sein d'une section en exclusion mutuelle.

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
*/


void FredThreadWakeUp( FredQueue* q)
{
		if(!FredQueueEmpty(q))FredQueuePutLast(&readyQ,FredQueueGetFirst(q));
}





/*! \ingroup IGthread
\brief Code initial des threads

	Comme on ne peut retourner de façon normale de la procédure initiale d'un thread et comme le retour de celle-ci est le retour standard généré par le compilateur C, on utilise une procédure  qui encadre (harnache) le démarrage et la fin d'un thread. 
	
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation

	
Ce harnais appelle la procédure initiale du thread, récupére son résultat et délégue à #leon la libération de la pile sans oublier les masquages et décomptages nécessaires.
*/


void FredThreadHarness()
{/* on démarre masqué */
	FredThread old;
	/* lancer le décomptage */
	FredCountDownStart(quantum);
	/* démasquage */ 
	FredMaskOff(); 
	/* appel de la procédure initiale avec ou sans récupération dec résultat */
	if(current->resref){ /* on attend un résultat */
			*(current->resref)=current->entry(current->val);

	}else{		
			current->entry(current->val);
	}
	/* arrêt décomptage */
	FredCountDownStop(); 
	/* début exclusion mutuelle */
	FredMaskOn();
	/* réveil de leon */
	FredThreadWakeUp(&leonQ);
	/* 
		un thread ne peut libérer la mémoire
		dans laquelle il s'exécute. Il délègue cela 
		a leon  en s'inscrivant dans la
		file des threads défunts
	*/
	FredThreadSuspend(&deadQ);
	/* on ne revient jamais la */
  FredAbort(" erreur fatale dans threadHarness");
}


/***************************************************************/  
/*! \ingroup Gthread
\brief Création et lancement d'un thread.
\param stacksz taille de pile exigée pour le thread
\param function fonction à exécuter par le thread
\param arg   adresse de l'argument de la fonction
\param resref Si l'adresse resref est non nulle, on y mettra l'adresse du résultat  de la fonction lorsque le thread créé terminera.
\return l'adresse du descripteur du thread créé ou FredTHREAD_NIL (0)

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation

On alloue la mémoire pour un descripteur de thread et la pile (éventuellement arrondi ). Si cette étape est franchi avec succès, on initialise le descripteur du thread ( #FredThreadDesc ) et son contexte.  On l'insère enfin dans la file des prêts #readyQ en incrémentant le nombre de threads vivants #nbFredThreads .

*/

FredThread   FredThreadSpawn(int stacksz,
								FredRunnable function,
                void *arg,void ** resref)
{
	FredThread nw;
	/* allocation d'un descripteur de thread si possible */
	nw= FredNEW(FredThreadDesc) ;
  if(!nw) return nw;
  /* calcul taille de pile */
  nw->stacksz=FredSTACK_SIZE(stacksz); 
	/* allocation de la pile  si possible */
  if(!(nw->stack=(char*)FredAtomicAlloc(nw->stacksz))) { 
		/* plus de mémoire */
    FredDELETE( nw); return FredTHREAD_NIL;
	}
	/* initialisation du descripteur */
	nw->err=0; /* init errno à OK */
	nw-> entry=function; /* fonction à exécuter */
	nw->val=arg; /* son argument */
  nw->resref=resref; /* où mettre le résultat */
	/* initialisation du contexte processeur du thread */ 
	FredContextInit(nw->ctx,
			FredMAKE_SP(nw->stack,nw->stacksz),
			FredThreadHarness);
	FredMaskOn();/* début d'exclusion mutuelle */
	FredQueuePutLast(&readyQ,nw);/* réveil du nouveau thread */
	nbFredThreads++; /* un thread de plus !! */
	FredMaskOff();/* fin d'exclusion mutuelle */
	return nw;
}





/***************************************************************/  
/*! \ingroup Gthread
\brief Terminaison immédiate du thread courant
\param res adresse du résultat du calcul du thread

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
	
On rend le résultat à l'endrioit défini au mau moment de la création du thread par FreedThreadSpawn() . Le thread est mis dans la file des décédés pour que sa mémoire soit libérer par #leon
*/

void   FredThreadExit(void * res)
{
  FredThread old;
	FredCountDownStop();/* arrêt décomptage */
	/* mettre le résultat à l'endroit demandé */
  if(current->resref)*(current->resref)=res; 
	FredMaskOn();/* début d'exclusion mutuelle */
	/* réveil de leon */
	FredThreadWakeUp(&leonQ);
	/* 
		un thread ne peut libérer la mémoire
		dans laquelle il s'exécute. Il délègue cela 
		a leon  en s'inscrivant dans la
		file des threads défunts
	*/
	FredThreadSuspend(&deadQ);
	/* on ne revient jamais ici */
	FredAbort(" erreur fatale dans threadDie");
}
/***************************************************************/  
/*! \ingroup Gthread
\brief Le thread courant passe la main à un autre thread

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
*/

void FredThreadYield( )
{
	FredThread old;
	FredMaskOn(); /* début d'exclusion mutuelle */
	current->err=errno; /* sauver errno */
  FredCountDownStop();/* arrêt décomptage */
	old=current;
	/* le courant se met en queue des prêts */
	/* on tire un nouveau courant */
	FredQueuePutLast(&readyQ,current);
	current=FredQueueGetFirst(&readyQ);
	/* traiter le cas d'un unique thread actif : rien faire */
	if(current!=old)FredContextSwitch(old->ctx,current->ctx);
	/* 
		on reviendra ici lors de son réveil :
		on relance le décomptage du quantum
 	*/
	FredCountDownStart(quantum);/* lancer décomptage */
	errno=current->err;/* restaurer errno */
	FredMaskOff();/* fin d'exclusion mutuelle */
}

/***************************************************************/  
/*! \ingroup Gthread
\brief Rend l'identité du thread courant
\return l'adresse du descripteur du thread actif                        

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation

*/
FredThread	FredThreadCurrent()
{
	return current;
}
 

/***************************************************************/  

/**************************************************************
* Initialisation mainThread
**************************************************************/

/*! \ingroup IGthread
\brief Initialisation du pseudo thread principal

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
	La procédure principale main() initialise la file d'attente de terminaison, son bloc descripteur et devient le thread courant. A partir de la, elle peut utiliser l'interface FRED comme tout thread normal (synchronisation etc..).
Elle peut attendre la terminaison des autres threads via la file #endQ (cf. FredWaitTerminate() ).
*/
void MainThreadInit(){
	/* file d'attente de disparition des autres threads */
	FredQueueInit(&endQ); /*  file vide */
	/* allocation decripteur */
	mainThread=&mainThreadBlock;
	/* thread actif */
	current=mainThread;
}



/*! \ingroup Ginit
\brief Initialisation de FRED

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
	La procédure principale main() initialise les variables d'état des différents modules de base, se transforme en un thread principal et lance le nettoyeur leon et le veilleur watchdog. 
*/
 void FredInitialize()
{
	FredMasksInit(); /* initialiser les masques de signaux */
	FredSignalsInit();/* initialiser les traitants de signaux */
	nbFredThreads=0;/* aucun thread n'existe */
	FredQueueInit(&readyQ); /* file vide */
	TimeSharingInit(250); /* initialiser le temps partagé */
	MainThreadInit(); /* forger un pseudo thread principal */
	LeonInit(); /* créer le nettoyeur */
	WatchDogInit(); /* créer le chien de garde */
}
/***************************************************************/  
/*! \ingroup Ginit
\brief  Attente de fin d'exécution de tous les threads créés

\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
Le thread principal se bloque s'il y d'autres threads que le watchdog et leon. Sinon il termine.
*/
 void  FredWaitTerminate()
{ int nonfini=1;
  while(nonfini){
		FredMaskOn();/* début d'exclusion mutuelle */
		/* Il y a 2 threads de servitude : leon et watchdog */
		if(nbFredThreads>2) /* il y a d'autres threads */
			/* suspension dans la file d'attente de fin */
			FredThreadSuspend(&endQ);
		else	/* il n'y a plus que  leon et watchdog */
			nonfini=0;
		FredMaskOff();/* fin d'exclusion mutuelle */
	}
}
/***************************************************************/  

/*! \ingroup Ginit
\brief Arrêt brutal du programme et impression d'un message d'erreur
\param mess message d'erreur fatale


\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\htmlonly <hr WIDTH="100%"> \endhtmlonly 
\b Implantation
*/
/***************************************************************/  
void  FredAbort(char *mess )
{
  printf(" \n FATAL  ERROR : ");
  printf(mess);
  printf("\n");
  exit(1); /* fin du programme */
}


