
/*

Program:	Idle Led
Version:	2.1a	1/3/94
Author:		Lindsay Meek (meek@fizzy.csu.murdoch.edu.au)

Description:	Whenever the CPU is busy, the power led goes on else it is off.

		This is the second version that operates by attaching itself
		to the vertical blanking interrupt. A low priority (-127)
		task is active which increments a counter and turns off the
		light. When this task does not run, the vblank irq detects
		the non-incrementing counter and turns the light on.

		This version is more system friendly than 1.0

		The CPU must be busy for more than 1 vbi for the
		light to activate
*/

#include <exec/nodes.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <hardware/cia.h>
#include <hardware/custom.h>
#include <hardware/intbits.h>
#include <libraries/dos.h>

/* Assembly stub */
extern void IdleIrq(void);
extern void LightState(UWORD state);

/* A string print (no formatting) that doesn't use as much memory as printf */
void zprint(char *string)
{
	extern BPTR Output(void);
	extern long Write(BPTR file, char *buffer, long length);

	Write(Output(),string,strlen(string));

	exit(1);
}

static UWORD idlelight_flag;	/* Flag saying to turn the light on or off */

/* Make the light 'active' depending on the idle flag */
void LightActive(void)
{
	LightState(idlelight_flag);
}

/* Make the light 'inactive' depending on the idle flag */
void LightInactive(void)
{
	LightState(!idlelight_flag);
}

/* System structure names */
#define UTILITY_NAME "Idle_LED"

/* various counters used to detect the idle state in the vblank */
static ULONG idle_vbi_cnt_limit[3];

/* Run this as a process. It aborts when it detects a CTRL_C signal */
main(int argc,char **argv)
{
	extern struct Task *FindTask(char *);
	struct Task *met;
	struct Interrupt mei;
	UWORD threshold;
	WORD oldpri;
	char *oldname;
	int i,kill_flag;

	threshold=50;		/* Default: CPU threshold: > 50 % */
	idlelight_flag=1;	/* Default: Make light come on for a busy cpu */
	kill_flag=0;		/* Don't kill it */

	for(i=1;i<argc;i++)
	{
		if(!stricmp(argv[i],"off")) kill_flag=1;	/* disable */
		else
		if(!stricmp(argv[i],"dim")) idlelight_flag=0;	/* dim = busy */
		else
		if(!stricmp(argv[i],"threshold"))
		{
			if(++i > argc) 
				zprint("Missing argument");

			threshold=atoi(argv[i]);

			/* clip threshold to stop silly buggers */
			if(threshold < 1) threshold=1;
			if(threshold > 99) threshold=99;
		}
		else
		if(stricmp(argv[i],"on") && stricmp(argv[i],"bright"))
		{	
			zprint("Usage: Idle_LED {ON} {OFF} {BRIGHT} {DIM} {THRESHOLD n}\n");
		}
	}

	met=FindTask(UTILITY_NAME);

	if(kill_flag)
	{
		if(met==NULL)
			zprint("Idle_LED not running!\n");

		/* Send CTRL-C signal to task */
		Signal(met,SIGBREAKF_CTRL_C);

		zprint("Idle_LED terminating\n");
	}

	if(met!=NULL)
		zprint("Idle_LED already running!\n");

	mei.is_Node.ln_Type = NT_INTERRUPT;
	mei.is_Node.ln_Pri  = -127;
	mei.is_Node.ln_Name = UTILITY_NAME;
	mei.is_Data = (APTR)idle_vbi_cnt_limit;
	mei.is_Code = IdleIrq;

	/* reset counter,last state */

	AddIntServer(INTB_VERTB, &mei);		/* Install IRQ server */

	met=FindTask(NULL);		/* Fetch this tcb */
	oldpri=met->tc_Node.ln_Pri;	/* Save old priority */
	oldname=met->tc_Node.ln_Name;	/* Save old name */
	met->tc_Node.ln_Name=UTILITY_NAME;	/* Redirect task name */
	met->tc_Node.ln_Pri=40;		/* Raise priority for calibration */

	idle_vbi_cnt_limit[0]=0;
	idle_vbi_cnt_limit[1]=0;
	idle_vbi_cnt_limit[2]=0;

	/* wait for next vbi */
	while(idle_vbi_cnt_limit[0]==0) ;

	/* count # of increments in one frame (for calibration) */
	idle_vbi_cnt_limit[2]=0;
	idle_vbi_cnt_limit[0]=0;
	while(idle_vbi_cnt_limit[0] == 0) 
		idle_vbi_cnt_limit[2]++;

	/* > threshold % load is busy */
	idle_vbi_cnt_limit[2] = (idle_vbi_cnt_limit[2] * threshold)/100;

	met->tc_Node.ln_Pri=-127;	/* Make priority VERY low for idle */

	/* Busy loop for eating idle cpu cycles */
	while((met->tc_SigRecvd & SIGBREAKF_CTRL_C)==0)	/* Abort on CTRL_C */
	{
		/* Increment idle counter */
		idle_vbi_cnt_limit[1]++;
	}

	RemIntServer(INTB_VERTB,&mei);			/* Remove IRQ server */

	met->tc_Node.ln_Pri=oldpri;			/* Change priority back */
	met->tc_Node.ln_Name=oldname;			/* Change name back */

	/* Turn on power led */
	LightState(1);

	return 0;
}

#asm

;
;Assembly section for vertical blanking interrupt server
;
;You may have to put this into a seperate .asm file and compile to get it working
;with your C compiler
;

	include	"hardware/cia.i"

_ciaa	=	$bfe001		;i defined it here so i could use the small model!

	public	_IdleIrq
	public	_LightActive
	public	_LightInactive
	public	_LightState
	
	public	_geta4

_IdleIrq:

	move.l	a4,-(sp)	;save a4
	jsr	_geta4		;retrieve a4 for global variables

	addq.l	#1,(a1)+

	move.l	(a1),d0		;idle_cnt
	clr.l	(a1)+		;=0
	cmp.l	(a1),d0		;>=idle_limit
	bge.s	_IdleIrq1	;yes - then its idle

	jsr	_LightActive	;turn on light - cpu is busy

	move.l	(sp)+,a4

	moveq	#0,d0		;permit other servers to execute
	rts			

_IdleIrq1:

	jsr	_LightInactive	;turn off light - cpu is idle
	
	move.l	(sp)+,a4

	moveq	#0,d0		;permit other servers to execute
	rts

;
;void LightState(UWORD state)
;
;
;The routine is coded in assembly to prevent the C compiler from
;splitting up the read-modify-write cycle (bad news for hardware)
;
_LightState:

	tst.w	4(sp)			;check state
	beq.s	ls1			;off?
	bclr.b	#CIAB_LED,_ciaa+ciapra	;turn on power light (cpu is busy)
	rts

ls1:	bset.b	#CIAB_LED,_ciaa+ciapra	;turn off power light (cpu is idle)
	rts

#endasm
