*
* Load Device	  by Jeff Kelley
*
*   You may give this software to anyone you please.  If you sell it for
*  a profit, you'll just have to live with your conscience.
*
*	     (in W.W. Howe V1.0.2 Assembler)
*
LOAD_TASK_PRIORITY  EQU 25
LOAD_PRIORITY	    EQU 0
MICROSPERSEC	EQU 1000000
INTERVAL	EQU 5  ; Default time interval length in seconds (sample period)
TICKS		EQU 61 ; Default number of ticks (when samples are taken)  per interval

	INCLUDE "exec/types.i"
	INCLUDE "exec/nodes.i"
	INCLUDE "exec/lists.i"
	INCLUDE "exec/ports.i"
	INCLUDE "exec/libraries.i"
	INCLUDE "exec/devices.i"
	INCLUDE "exec/memory.i"
	INCLUDE "exec/io.i"
	INCLUDE "exec/resident.i"
	INCLUDE "exec/tasks.i"
	INCLUDE "exec/errors.i"
	INCLUDE "exec/initializers.i"
	INCLUDE "exec/semaphores.i"
	INCLUDE "exec/execbase.i"
	INCLUDE "hardware/dmabits.i"
	INCLUDE "hardware/custom.i"
	INCLUDE "devices/timer.i"
	INCLUDE "devices/load.i"
	INCLUDE "asmsupp.i"
	INCLUDE "macros.i"

	XLIB	OpenDevice
	XLIB	OpenLibrary
	XLIB	AddDevice
	XLIB	AddTask
	XLIB	CloseDevice
	XLIB	Signal
	XLIB	Remove
	XLIB	RemTask
	XLIB	PutMsg
	XLIB	GetMsg
	XLIB	ReplyMsg
	XLIB	Wait
	XLIB	DoIO
	XLIB	AvailMem
	XLIB	FreeMem
	XLIB	Disable
	XLIB	Enable
	XLIB	Forbid
	XLIB	Permit
	XLIB	AllocSignal
	XLIB	InitSemaphore
	XLIB	ObtainSemaphore
	XLIB	ReleaseSemaphore
	XLIB	SetTaskPri
	XREF	_AbsExecBase
	XREF	_custom

	SECTION load.device,CODE

FirstAddress:
	CLEAR	d0
	rts

initDDescrip:
	DC.W	RTC_MATCHWORD
	DC.L	initDDescrip
	DC.L	EndCode
	DC.B	RTF_AUTOINIT
	DC.B	VERSION
	DC.B	NT_DEVICE
	DC.B	LOAD_PRIORITY
	DC.L	LoadName
	DC.L	idString
	DC.L	Init

VERSION EQU	1
REVISION EQU	0

TimerName   TIMERNAME
LoadName    LOADNAME

idString:
	DC.B	'load.device 1.0 (16 October 1987)',13,0

Init:
	DC.L	LD_SIZE
	DC.L	funcTable
	DC.L	dataTable
	DC.L	initRoutine

funcTable:
	DC.L	Open
	DC.L	Close
	DC.L	Expunge
	DC.L	FirstAddress	; Null Command (reserved for expansion)

	DC.L	BeginIO
	DC.L	AbortIO

	DC.L	-1

dataTable:
	INITBYTE    LN_TYPE,NT_DEVICE
	INITLONG    LN_NAME,LoadName
	INITBYTE    LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
	INITWORD    LIB_VERSION,VERSION
	INITWORD    LIB_REVISION,REVISION
	INITLONG    LIB_IDSTRING,idString
	DC.L	    0

*********************************************************************
*
* InitRoutine - initialize the load device structure
*
*	  D0 = Device Pointer
*	  A0 = Segment List
*	  A6 = SysBase
*	  If this routine returns non-zero, it will be linked into system
*	  device list.


initRoutine:
	move.l	a5,-(sp)
	movea.l d0,a5		; get device pointer into safe register

	move.l	a0,LD_seglist(a5)
	move.l	a6,LD_sysbase(a5)

*   Initialize the Message Port.
	lea	LD_Port(a5),a0
	move.l	#LoadName,LN_NAME(a0)
	move.b	#NT_MSGPORT,LN_TYPE(a0)
	move.b	#PA_IGNORE,MP_FLAGS(a0)
	lea	MP_MSGLIST(a0),a0
	NEWLIST a0

	move.w	#INTERVAL,LD_Interval(a5)
	move.w	#TICKS,LD_Ticks(a5)
	move.l	a6,-(sp)
	movea.l a5,a6
	bsr	ComputeDelay
	move.l	(sp)+,a6

*   Initialize the semaphore.
	lea	LD_Semaphore(a5),a0
	CALLSYS InitSemaphore	    ; InitSemaphore(signalSemaphore)
				    ;		    A0
	lea	LD_Semaphore(a5),a0
	CALLSYS ObtainSemaphore     ; ObtainSemaphore(signalSemaphore)
				    ;		      A0

*   Compute the maximum MEMF_CHIP and MEMF_FAST
	move.l	MemList+LH_HEAD(a6),a0 ; get pointer to first MemHeader node
	DO	addmemory
	    move.l  (a0),d1	       ; check LN_SUCC
	UNTIL	eq,addmemory
	    move.l  MH_UPPER(a0),d0
	    sub.l   MH_LOWER(a0),d0
	    btst    #MEMB_CHIP,MH_ATTRIBUTES+1(a0)
	    IF	    ne,chipmemory
		add.l	d0,LD_Max_Chip(a5)
	    ELSE    chipmemory
		add.l	d0,LD_Max_Fast(a5)
	    FI	    chipmemory
	    move.l  d1,a0
	OD	addmemory

* Create the Load Device task.
	lea	LD_TaskCB(a5),a1
	move.b	#NT_TASK,LN_TYPE(a1)
	move.b	#LOAD_TASK_PRIORITY,LN_PRI(a1)
	move.l	#LoadName,LN_NAME(a1)
	lea	LD_Stack(a5),a0
	move.l	a0,TC_SPLOWER(a1)
	add.l	#LOAD_STACK_SIZE,a0
	move.l	a0,TC_SPUPPER(a1)
	move.l	a0,TC_SPREG(a1)
	lea	TC_MEMENTRY(a1),a0
	NEWLIST a0
	lea	Task_Start,a2
	movea.l #0,a3
	CALLSYS AddTask 	    ; AddTask(taskCB, initialPC, finalPC)
				    ;	      A1      A2	 A3
	move.l	a5,d0		    ; Return device pointer

	move.l	(sp)+,a5	    ; restore a5
	rts

***************************************************************************
*
* Open
*
*	  A6 = Device
*	  A1 = IOB
*	  D0 = Unit Number
*	  D1 = Flags
*  Return: Set IO_ERROR Field to indicate success/errno

Open:
	btst	#LDB_OPEN_EXCL,LD_flags(a6)
	IF	eq,shared_access_ok
	    btst    #LDB_OPEN_EXCL,d1
	    IF	    ne,want_exclusive
		tst.w	LIB_OPENCNT(a6)
		IF	ne,device_in_use
		    move.b  LDERR_IN_USE,IO_ERROR(a1)
		    rts
		FI	device_in_use
		bset	#LDB_OPEN_EXCL,LD_flags(a6)
	    FI	    want_exclusive
	    addq.w  #1,LIB_OPENCNT(a6)
	    cmpi.w  #1,LIB_OPENCNT(a6)
	    IF	    eq,startup
		lea	LD_Semaphore(a6),a0
		LINKSYS ReleaseSemaphore    ; ReleaseSemaphore(signalSemaphore)
					    ;		       A0
	    FI	    startup
	    move.w  LD_Interval(a6),LV_INTERVAL(a1)
	    move.w  LD_Ticks(a6),LV_TICKS(a1)
	    move.b  LD_TaskCB+LN_PRI(a6),LV_PRI(a1)
	    clr.b   IO_ERROR(a1)
	ELSE	shared_access_ok
	    move.b  #LDERR_ACCESS_DENIED, IO_ERROR(a1)
	FI	shared_access_ok
	rts

***************************************************************************
*
* Close
*
*	  A6 = Device
*	  A1 = IOB
*  Return: If the device is no longer open and a delayed expunge is pending,
*	   do the expunge and return the segment list.	Else return NULL.

Close:
	clr.l	IO_DEVICE(a1)
	subq.w	#1,LIB_OPENCNT(a6)
	IF	eq,do_close
	    lea     LD_Semaphore(a6),a0
	    LINKSYS ObtainSemaphore	; ObtainSemaphore(signalSemaphore)
					;		  A0
	    bclr    #LDB_OPEN_EXCL,LD_flags(a6)
	    btst    #LIBB_DELEXP,LIB_FLAGS(a6)
	    IF	    ne,go_expunge
		bra.s	Expunge
	    FI	    go_expunge
	FI	do_close
	CLEAR	d0
	rts

****************************************************************************
*
* Expunge
*
*	  A6 = Device
*  Return: If the device is no longer open return seg list. Else set
*	   delayed Expunge flag and return NULL.
Expunge:

	move.l	a5,-(sp)
	move.l	a6,a5
	move.l	LD_sysbase(a5),a6
*	 Check to see if anyone has us open

	tst.w	LIB_OPENCNT(a5)
	IF	ne,still_open

*	It is still open, set delayed Expunge flag

	    bset    #LIBB_DELEXP,LIB_FLAGS(a5)
	    CLEAR   d0

	ELSE	still_open

*	Go ahead and get rid of us.

	    move.l  LD_seglist(a5),-(a7)    ; We need to return this value

*	Unlink from device list.
	    movea.l a5,a1
	    CALLSYS Remove		    ; Remove(node)
					    ;	     A1
*	Remove the load.device task
	    lea     LD_TaskCB(a5),a1
	    CALLSYS RemTask		    ; RemTask(taskCB)
					    ;	      A1
*	Close the timer device.
	    lea     LD_TimerRequest(a5),a1
	    CALLSYS CloseDevice 	    ; CloseDevice(ioRequest)
					    ;		  A1
*	Free the memory allocated to the library.
	    CLEAR   d0
	    movea.l a5,a1
	    move.w  LIB_NEGSIZE(a5),d0
	    sub.w   d0,a1
	    add.w   LIB_POSSIZE(a5),d0
	    CALLSYS FreeMem		    ; FreeMem(memBlock, byteSize)
					    ;	      A1	D0
	    move.l  (a7)+,d0
	FI  still_open
	move.l a5,a6
	move.l (sp)+,a5
	rts

*************************************************************************
*
* BeginIO
*
*	  A6 = Device
*	  A1 = IOB

BeginIO:
	clr.b	IO_ERROR(a1)
	move.w	IO_COMMAND(a1),d0
	asl.w	#2,d0		    ; Compute displacement
	lea	cmdtable(pc),a0
	add.w	d0,a0
*	cmpa.l	#cmdtable_end,a0    ; Check to make sure it's in range.
* (Assembler doesn't handle it right, so...)
	dc.w	$B1FC
	dc.l	cmdtable_end
	bcs.s	cmd_ok		    ; branch if a0 < #cmdtable_end
	lea	cmdtable(pc),a0     ; Get the address of CMD_INVALID
cmd_ok:
	movea.l (a0),a0
	jmp	(a0)

****************************************************************************
*
* AbortIO
*
*	  A6 = Device
*	  A1 = IOB
AbortIO:
* Scan list for the IOB node
	LINKSYS Forbid
	move.l	LD_Port+MP_MSGLIST+LH_HEAD(a6),d1
	DO	scanlist
	    movea.l d1,a0
	    move.l  (a0),d1	      ; get LN_SUCC
	    beq.s   not_found
	    cmpa.l  a0,a1
	WHILE	ne,scanlist
	OD	scanlist

	move.l	a1,d1
	REMOVE	a1		    ; Remove the IOB node from the list.
	LINKSYS Permit
	movea.l d1,a1
	move.b	#IOERR_ABORTED,IO_ERROR(a1)
	rts
not_found:
	LINKSYS Permit
	clr.b  IO_ERROR(a1)	    ; Indicate an error occurred.
	rts

cmdtable:
	DC.L	Invalid
	DC.L	Reset
	DC.L	Read
	DC.L	Write
	DC.L	Update
	DC.L	Clear
	DC.L	Stop
	DC.L	Start
	DC.L	Flush
	DC.L	Set
cmdtable_end:

*******************************************************************
*
*	A6 = device
*	A1 = IOB

Write:
Update:
Clear:
Invalid:
	move.b	#IOERR_NOCMD,IO_ERROR(a1)
	rts

Read:
* If IO_QUICK is set, get the most recent values and return, do not reply.
* Otherwise, queue the IO request. Return.  Reply will come later.

	btst	#IOB_QUICK,IO_FLAGS(a1)
	IF	ne,do_quickio
	    move.l  LD_CPU(a6),LV_CPU(a1)
	    move.l  LD_BLITTER(a6),LV_CPU(a1)
	    move.l  LD_CHIP(a6),LV_CHIP(a1)
	    move.l  LD_FAST(a6),LV_FAST(a1)
	ELSE	do_quickio
	    lea     LD_Port(a6),a0
	    LINKSYS PutMsg		; PutMsg(msgPort, message)
	FI	do_quickio		;	 A0	  A1
	rts
Stop:
	LINKSYS Forbid
	tst.b	LD_stop_count(a6)
	IF	eq,stop_load_task
	    lea     LD_Semaphore(a6),a0
	    LINKSYS ObtainSemaphore
	FI	stop_load_task
	addq.b	#1,LD_stop_count(a6)
	LINKSYS Permit
	rts
Start:
	tst.b	LD_stop_count(a6)
	IF	eq,not_stopped
	    move.b  #LDERR_NOT_STOPPED,IO_ERROR(a1)
	ELSE	not_stopped
	    LINKSYS Forbid
	    subq.b  #1,LD_stop_count(a6)
	    IF	    eq,start_load_task
		lea	LD_Semaphore(a6),a0
		LINKSYS ReleaseSemaphore
	    FI	    start_load_task
	    LINKSYS Permit
	FI	not_stopped
	rts

* Reply to all pending requests, setting IOERR_ABORTED.
Flush:
	LINKSYS Forbid
	DO	flushrequests
	    lea     LD_Port(a6),a0
	    LINKSYS GetMsg	    ; message = GetMsg(msgPort)
	    tst.l   d0		    ; D0	       A0
	UNTIL	 eq,flushrequests
	    movea.l d0,a1
	    move.b  #IOERR_ABORTED,IO_ERROR(a1)
	    LINKSYS ReplyMsg	    ; ReplyMsg(message)
				    ;	       A1
	OD	flushrequests
	LINKSYS Permit
	rts

Reset:
;   Set default values.
	move.w	#INTERVAL,LV_INTERVAL(a1)
	move.w	#TICKS,LV_TICKS(a1)
	move.b	#LOAD_TASK_PRIORITY,LV_PRI(a1)
;   FALLTHROUGH into the Set command

Set:
	move.l	a1,-(sp)
	lea	LD_Semaphore(a6),a0
	LINKSYS ObtainSemaphore     ; ObtainSemaphore(signalSemaphore)
				    ;		      A0
	move.l	(sp)+,a1
	move.w	LV_INTERVAL(a1),LD_Interval(a6)
	move.w	LV_TICKS(a1),LD_Ticks(a6)
	bsr.s	ComputeDelay
	move.b	LV_PRI(a1),d0
	lea	LD_TaskCB(a6),a1
	LINKSYS SetTaskPri	    ; oldPriority = SetTaskPri(taskCB, newpriority)
				    ; D0.b		       A1      D0.b
	lea	LD_Semaphore(a6),a0
	LINKSYS ReleaseSemaphore    ; ReleaseSemaphore(signalSemaphore)
				    ;		       A0
	rts

***************************************************************************
*
* ComputeDelay subroutine - From the LD_Interval and LD_Ticks, compute the
*			    delay in seconds and microseconds between ticks.
*
*   Input:   A6 - Load Device
*	     LD_Interval(a6) - Interval in seconds
*	     LD_Ticks(a6)    - Number of Ticks per Interval
*
*   Output:  LD_secs(a6)     - delay in seconds
*	     LD_micro(a6)    - delay in microseconds
*
*   Destroys: a0,d0,d1
*

ComputeDelay:
	move.l	d2,-(a7)
	CLEAR	d1
	move.w	LD_Interval(a6),d1	    ; INTERVAL in seconds
	move.w	LD_Ticks(a6),d2 	    ; number of TICKS per INTERVAL
	mulu	#20,d1			    ; multiply d0 by a MICROSPERSEC
	mulu	#50000,d1		    ; (in 2 steps, since can't mult by >65536)

*	d1.l = d1.l / d2.w

	movea.w d1,a0
	clr.w	d1
	swap	d1
	divu	d2,d1
	move.l	d1,d0
	swap	d1
	move.w	a0,d0
	divu	d2,d0
	move.w	d0,d1

*	LD_secs  = d1.l / MICROSPERSEC
*	LD_micro = d1.l % MICROSPERSEC

	clr.l	LD_secs(a6)
	DO	incrsecs
	    cmp.l #MICROSPERSEC,d1
	WHILE	ge,incrsecs
	    sub.l #MICROSPERSEC,d1
	    addi.l #1,LD_secs(a6)
	OD	incrsecs
	move.l	d1,LD_micro(a6)

	move.l	(a7)+,d2
	rts

********************************************************************
*
* Load Task
*
* Register Usage:   a6 : SysBase
*		    a2 : Semaphore
*		    a3 : TimerPort
*		    a4 : TimerMessage
*		    a5 : LoadDevice
*
*		    d2 : Current Tick count
*		    d3 : Ready Queue counter
*		    d4 : Blitter Use counter
*		    d5 : Chip Mem available
*		    d6 : Fast Mem available
*		    d7 : <Free Safety>

*  To get a pointer to the Load Device structure, use a7 as a base.
*  It points to the last longword of the stack (which contains the
*  address of a cleanup routine).
*									a7
*  |	<---------- LD_Stack ----------->   | <- LOAD_STACK_SIZE - 4 ->  |
*  |----------------------------------------------------------------------|
*  | Load Device   | TimerRequest | etc.    | Stack			  |
*  |----------------------------------------------------------------------|

Task_Start:
	move.l	_AbsExecBase,a6
	lea	-LD_Stack-LOAD_STACK_SIZE+4(a7),a5   ; Get Load Device ptr

	lea	LD_Semaphore(a5),a2

*   Initialize the Timer Port.
	lea	LD_TimerPort(a5),a3
	move.b	#NT_MSGPORT,LN_TYPE(a3)
	move.b	#PA_SIGNAL,MP_FLAGS(a3)
	moveq.l #-1,d0
	CALLSYS AllocSignal		; signalNum = AllocSignal(signalNum)
	move.b	d0,MP_SIGBIT(a3)	; D0			  D0
	lea	LD_TaskCB(a5),a0
	move.l	a0,MP_SIGTASK(a3)
	lea	MP_MSGLIST(a3),a0
	NEWLIST a0

*   Initialize the timer IO request
	lea	LD_TimerRequest(a5),a4
	move.b	#NT_MESSAGE,LN_TYPE(a4)
	move.l	a3,MN_REPLYPORT(a4)
	move.w	#IOTV_SIZE,MN_LENGTH(a4)

*   Open the timer.device
	lea	TimerName,a0
	movea.l a4,a1
	moveq.l #UNIT_VBLANK,d0
	CLEAR	d1
	CALLSYS OpenDevice    ; error = OpenDevice(Name,Unit,ioRequest,flags)
			      ; D0		   A0	D0   A1        D1
	DO	mainloop
	    move.l  a2,a0
	    CALLSYS ObtainSemaphore	; ObtainSemaphore(signalSemaphore)
	    move.w  LD_Ticks(a5),d2	;		  A0
	    moveq.l #0,d3
	    moveq.l #0,d4

*	    'tickloop' is done once for each 'tick'.
	    DO	    tickloop
		move.w	#TR_ADDREQUEST,IO_COMMAND(a4)
		move.l	LD_secs(a5),IOTV_TIME+TV_SECS(a4)
		move.l	LD_micro(a5),IOTV_TIME+TV_MICRO(a4)
		movea.l a4,a1
		CALLSYS DoIO		    ; error = DoIO(ioRequest)
					    ; D0	   A1
*		Compute System Statistics:

*		Measure length of ready queue
*		Note: LoadTask is not in this queue, since we are running.

		CALLSYS Disable
		movea.l TaskReady+LH_HEAD(a6),a1  ; Get Pointer to first
		DO	readycount		  ; node into a1.
		    move.l  (a1),d0		  ; Look ahead.
		UNTIL	eq,readycount
		    addq.l  #1,d3
		    movea.l d0,a1
		OD	readycount
		CALLSYS Enable

*		Check if blitter busy

*		move.w	_custom+dmaconr,d0	  ; Assembler can't handle this...
		move.w	$DFF002,d0
		btst	#DMAB_BLTDONE,d0
		IF  ne,busy
		    addq.l  #1,d4
		FI  busy

		subq.w	#1,d2		 ; Decrement tick count
	    UNTIL   eq,tickloop
	    OD	    tickloop

*	    Compute available memory

	    move.l  #MEMF_CHIP,d1
	    CALLSYS AvailMem		    ; size = AvailMem(requirements)
	    move.l  LD_Max_Chip(a5),d5	    ; D0	      D1
	    sub.l   d0,d5

	    move.l  #MEMF_FAST,d1
	    CALLSYS AvailMem		    ; size = AvailMem(requirements)
	    move.l  LD_Max_Fast(a5),d6	    ; D0	      D1
	    sub.l   d0,d6
	    move.l  d3,LD_CPU(a5)
	    move.l  d4,LD_BLITTER(a5)
	    move.l  d5,LD_CHIP(a5)
	    move.l  d6,LD_FAST(a5)

	    DO	    reply
		lea	LD_Port(a5),a0
		CALLSYS GetMsg		    ; message = GetMsg(msgPort)
		tst.l	d0		    ; D0	       A0
	    UNTIL    eq,reply
		movea.l d0,a1
		move.l	d3,LV_CPU(a1)
		move.l	d4,LV_BLITTER(a1)
		move.l	d5,LV_CHIP(a1)
		move.l	d6,LV_FAST(a1)
		CALLSYS ReplyMsg	    ; ReplyMsg(message)
	    OD	    reply		    ;	       A1

	    lea     LD_Semaphore(a5),a0
	    CALLSYS ReleaseSemaphore	    ; ReleaseSemaphore(signalSemaphore)
	ODL	mainloop		    ;		       A0

EndCode:
	END
