
*************************************************************************
*									*
*	Copyright (C) 1986, Commodore Amiga Inc.  All rights reserved.	*
*	Permission granted for non-commercial use			*								*
*									*
*************************************************************************


*************************************************************************
*
* mydev.asm -- Skeleton device code.  Modified by Lee Erickson to be a 
*	       simple disk device, using RAM to simulate a disk.
*	       10/7/86
*
************************************************************************/
	SECTION	section

	NOLIST
	include "exec/types.i"
	include "exec/nodes.i"
	include "exec/lists.i"
	include "exec/libraries.i"
	include "exec/devices.i"
	include "exec/io.i"
	include "exec/alerts.i"
	include "exec/initializers.i"
	include "exec/memory.i"
	include "exec/resident.i"
	include "exec/ables.i"
	include "exec/errors.i"
	include "exec/tasks.i"
	include	'libraries/expansion.i'
	include 'libraries/configvars.i'
	include 'libraries/configregs.i'

	include "asmsupp.i"
	include "messages.i"

	include "mydev.i"

	LIST

	;------ These don't have to be external, but it helps some
	;------ debuggers to have them globally visible
	XDEF	Init
	XDEF	Open
	XDEF	Close
	XDEF	Expunge
	XDEF	Null
	XDEF	myName
	XDEF	BeginIO
	XDEF	AbortIO

	XREF	_AbsExecBase

	XLIB	AddIntServer
	XLIB	RemIntServer
	XLIB	Debug
	XLIB	InitStruct
	XLIB	OpenLibrary
	XLIB	CloseLibrary
	XLIB	Alert
	XLIB	FreeMem
	XLIB	Remove
	XLIB	AllocMem
	XLIB	AddTask
	XLIB	PutMsg
	XLIB	RemTask
	XLIB	ReplyMsg
	XLIB	Signal
	XLIB	GetMsg
	XLIB	Wait
	XLIB	WaitPort
	XLIB	AllocSignal
	XLIB	SetTaskPri
	XLIB	GetCurrentBinding	; Get list of boards for this driver
	XLIB	MakeDosNode
	XLIB	AddDosNode

	INT_ABLES


	; The first executable location.  This should return an error
	; in case someone tried to run you as a program (instead of
	; loading you as a library).
FirstAddress:
	CLEAR	d0
	rts

;-----------------------------------------------------------------------
; A romtag structure.  Both "exec" and "ramlib" look for
; this structure to discover magic constants about you
; (such as where to start running you from...).
;-----------------------------------------------------------------------

	; Most people will not need a priority and should leave it at zero.
	; the RT_PRI field is used for configuring the roms.  Use "mods" from
	; wack to look at the other romtags in the system
MYPRI	EQU	0

initDDescrip:
					;STRUCTURE RT,0
	  DC.W    RTC_MATCHWORD		; UWORD RT_MATCHWORD
	  DC.L    initDDescrip		; APTR  RT_MATCHTAG
	  DC.L    EndCode		; APTR  RT_ENDSKIP
	  DC.B    RTF_AUTOINIT		; UBYTE RT_FLAGS
	  DC.B    VERSION		; UBYTE RT_VERSION
	  DC.B    NT_DEVICE		; UBYTE RT_TYPE
	  DC.B    MYPRI			; BYTE  RT_PRI
	  DC.L    myName		; APTR  RT_NAME
	  DC.L    idString		; APTR  RT_IDSTRING
	  DC.L    Init			; APTR  RT_INIT
					; LABEL RT_SIZE


	; this is the name that the device will have
subSysName:
myName:		MYDEVNAME

ExLibName	EXPANSIONNAME	; Expansion Library Name

	; a major version number.
VERSION:	EQU	1

	; A particular revision.  This should uniquely identify the bits in the
	; device.  I use a script that advances the revision number each time
	; I recompile.  That way there is never a question of which device
	; that really is.
REVISION:	EQU	17

	; this is an identifier tag to help in supporting the device
	; format is 'name version.revision (dd MON yyyy)',<cr>,<lf>,<null>
idString:	dc.b	'mydev 1.0 (31 Oct 1985)',13,10,0

	; force word allignment
	ds.w	0


	; The romtag specified that we were "RTF_AUTOINIT".  This means
	; that the RT_INIT structure member points to one of these
	; tables below.  If the AUTOINIT bit was not set then RT_INIT
	; would point to a routine to run.

Init:
	DC.L	MyDev_Sizeof		; data space size
	DC.L	funcTable		; pointer to function initializers
	DC.L	dataTable		; pointer to data initializers
	DC.L	initRoutine		; routine to run


funcTable:

	;------ standard system routines
	dc.l	Open
	dc.l	Close
	dc.l	Expunge
	dc.l	Null

	;------ my device definitions
	dc.l	BeginIO
	dc.l	AbortIO

	;------ function table end marker
	dc.l	-1


	; The data table initializes static data structures.
	; The format is specified in exec/InitStruct routine's
	; manual pages.  The INITBYTE/INITWORD/INITLONG routines
	; are in the file "exec/initializers.i".  The first argument
	; is the offset from the device base for this byte/word/long.
	; The second argument is the value to put in that cell.
	; The table is null terminated
dataTable:
	INITBYTE	LH_TYPE,NT_DEVICE
	INITLONG	LN_NAME,myName
	INITBYTE	LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
	INITWORD	LIB_VERSION,VERSION
	INITWORD	LIB_REVISION,REVISION
	INITLONG	LIB_IDSTRING,idString
	DC.L	0


	; This routine gets called after the device has been allocated.
	; The device pointer is in D0.  The segment list is in a0.
	; If it returns non-zero then the device will be linked into
	; the device list.
initRoutine:

; Register Usage
; ==============
; a3 -- Points to tempory RAM
; a4 -- Expansion library base
; a5 -- device pointer
; a6 -- Exec base
;
;----------------------------------------------------------------------
	;------ get the device pointer into a convenient A register
	PUTMSG	30,<'%s/Init: called'>
 	movem.l	d1-d7/a0-a5,-(sp)	; Preserve ALL modified registers
	move.l	d0,a5

	;------ save a pointer to exec
	move.l	a6,md_SysLib(a5)

	;------ save a pointer to our loaded code
	move.l	a0,md_SegList(a5)

	lea.l	ExLibName,A1		; Get expansion lib. name
	moveq.l	#0,D0
	CALLSYS	OpenLibrary		; Open the expansion library
	tst.l	D0
	bne.s	init_OpSuccess

init_OpFail:
	ALERT	AG_OpenLib!AO_ExpansionLib

init_OpSuccess:
	move.l	D0,A4

	lea	md_Base(A5),A0	; Get the Current Bindings
	moveq	#4,D0		; Just get address (length = 4 bytes)
	LINKLIB	_LVOGetCurrentBinding,A4
	move.l	md_Base(A5),D0		; Get start of list
	tst.l	D0			; If controller not found
	beq	Init_End		;	Exit and unload driver
	move.l	D0,A0			; Get config structure address
	move.l	cd_BoardAddr(A0),md_Base(A5); Save board base address
	bclr.b	#CDB_CONFIGME,cd_Flags(A0); Mark board as configured

Init_End:
;----------------------------------------------------------------------
;
; Here we build a packet describing the characteristics of our disk to
; pass to AmigaDOS.  This serves the same purpose as a "mount" command
; of this device would.  For disks, it might be useful to actually
; get this information right from the disk itself.  Just as mount,
; it could be for multiple partitions on the single physical device.
; For this example, we will simply hard code the appropriate parameters.
;
;-----------------------------------------------------------------------

;!!!! Normally you would only do this if your card was successfully configured
;!!!! up above.  For the demo, it will always be done.

	;------	Allocate tempory RAM to build MakeDosNode parameter packet
	move.l	#MEMF_CLEAR!MEMF_PUBLIC,d1
	move.l	#mdn_Sizeof,d0	; Enough room for our parameter packet
	CALLSYS	AllocMem
	move.l	d0,a3

	;-----	Use InitStruct to initialize the constant portion of packet
	move.l	d0,a2			; Point to memory to initialize
	moveq.l	#0,d0			; Don't need to re-zero it
	lea.l	mdn_Init(pc),A1
	CALLSYS	InitStruct

	lea	mdn_dName(a3),a0	; Get addr of Device name
	move.l	a0,mdn_dosName(a3)	;	and save in environment

	moveq	#1,d6			; Now tell AmigaDOS about all units
Uloop:
	move.b  d6,d0			; Get unit number
	add.b	#$2F,d0			; Make ASCII, minus 1
	move.b	d0,mdn_dName+3(a3)	;	and store in name
	move.l	d6,mdn_unit(a3)		; Store unit # in environment

	move.l	a3,a0
	LINKLIB	_LVOMakeDosNode,a4	; Build AmigaDOS structures
	move.l	d0,a0			; Get deviceNode address
	moveq.l	#0,d0			; Set device priority to 0
	moveq.l	#0,d1
*	moveq.l	#ADNF_STARTPROC,d1	; Have handler started
	LINKLIB	_LVOAddDosNode,a4

	addq	#1,d6			; Bump unit number
	cmp.b	#MD_NUMUNITS,d6
	ble.s	Uloop			; Loop until all units installed

	move.l	a3,a1		; Return RAM to system
	move.l	#mdn_Sizeof,d0
	CALLSYS	FreeMem

	move.l	a4,a1		; Now close expansion library
	CALLSYS	CloseLibrary
	;
	; You would normally set d0 to a NULL if your initialization failed,
	; but I'm not doing that for this demo, since it is unlikely
	; you actually have a board with this particular manufacturer ID
	; installed when running this demo.
	;
	move.l	a5,d0
	movem.l	(sp)+,d1-d7/a0-a5

	rts

;----------------------------------------------------------------------
;
; here begins the system interface commands.  When the user calls
; OpenLibrary/CloseLibrary/RemoveLibrary, this eventually gets translated
; into a call to the following routines (Open/Close/Expunge).  Exec
; has already put our device pointer in a6 for us.  Exec has turned
; off task switching while in these routines (via Forbid/Permit), so
; we should not take too long in them.
;
;----------------------------------------------------------------------


	; Open sets the IO_ERROR field on an error.  If it was successfull,
	; we should set up the IO_UNIT field.

Open:		; ( device:a6, iob:a1, unitnum:d0, flags:d1 )
	PUTMSG	30,<'%s/Open: called'>
	movem.l	d2/a2/a3/a4,-(sp)

	move.l	a1,a2		; save the iob

	;------ see if the unit number is in range
	subq	#1,d0		; Unit ZERO isn't allowed
	cmp.l	#MD_NUMUNITS,d0
	bcc.s	Open_Error	; unit number out of range

	;------ see if the unit is already initialized
	move.l	d0,d2		; save unit number
	lsl.l	#2,d0
	lea.l	md_Units(a6,d0.l),a4
	move.l	(a4),d0
	bne.s	Open_UnitOK

	;------ try and conjure up a unit
	bsr	InitUnit

	;------ see if it initialized OK
	move.l	(a4),d0
	beq.s	Open_Error

Open_UnitOK:
	move.l	d0,a3		; unit pointer in a3

	move.l	d0,IO_UNIT(a2)

	;------ mark us as having another opener
	addq.w	#1,LIB_OPENCNT(a6)
	addq.w	#1,UNIT_OPENCNT(a3)

	;------ prevent delayed expunges
	bclr	#LIBB_DELEXP,md_Flags(a6)
	moveq.l	#0,d0

Open_End:

	movem.l	(sp)+,d2/a2/a3/a4
	rts

Open_Error:
	move.b	#IOERR_OPENFAIL,IO_ERROR(a2)
	move.b	#IOERR_OPENFAIL,d0
	bra.s	Open_End

	; There are two different things that might be returned from
	; the Close routine.  If the device is no longer open and
	; there is a delayed expunge then Close should return the
	; segment list (as given to Init).  Otherwise close should
	; return NULL.

Close:		; ( device:a6, iob:a1 )
	movem.l	d1/a2-a3,-(sp)
	PUTMSG	30,<'%s/Close: called'>

	move.l	a1,a2

	move.l	IO_UNIT(a2),a3

	;------ make sure the iob is not used again
	moveq.l	#-1,d0
	move.l	d0,IO_UNIT(a2)
	move.l	d0,IO_DEVICE(a2)

	;------ see if the unit is still in use
	subq.w	#1,UNIT_OPENCNT(a3)

;!!!!!! Since this example is a RAM disk (and we don't want the contents to
;!!!!!! disappear between opens, ExpungeUnit will be skipped here.  It would
;!!!!!! be used for drivers of "real" devices
;!!!!!!	bne.s	Close_Device
;!!!!!!	bsr	ExpungeUnit

Close_Device:
	;------ mark us as having one fewer openers
	moveq.l	#0,d0
	subq.w	#1,LIB_OPENCNT(a6)

	;------ see if there is anyone left with us open
	bne.s	Close_End

	;------ see if we have a delayed expunge pending
	btst	#LIBB_DELEXP,md_Flags(a6)
	beq.s	Close_End

	;------ do the expunge
	bsr	Expunge

Close_End:
	movem.l	(sp)+,d1/a2-a3
	rts


	; There are two different things that might be returned from
	; the Expunge routine.  If the device is no longer open
	; then Expunge should return the segment list (as given to
	; Init).  Otherwise Expunge should set the delayed expunge
	; flag and return NULL.
	;
	; One other important note: because Expunge is called from
	; the memory allocator, it may NEVER Wait() or otherwise
	; take long time to complete.

Expunge:	; ( device: a6 )
	PUTMSG	30,<'%s/Expunge: called'>

	movem.l	d1/d2/a5/a6,-(sp)	; Best to save ALL modified registers
	move.l	a6,a5
	move.l	md_SysLib(a5),a6
	
	;------ see if anyone has us open
	tst.w	LIB_OPENCNT(a5)
;!!!!!  The following line is commented out for this RAM disk demo, since
;!!!!!  we don't want the RAM to be freed after FORMAT, for example.
;	beq	1$

	;------ it is still open.  set the delayed expunge flag
	bset	#LIBB_DELEXP,md_Flags(a5)
	CLEAR	d0
	bra.s	Expunge_End

1$:
	;------ go ahead and get rid of us.  Store our seglist in d2
	move.l	md_SegList(a5),d2

	;------ unlink from device list
	move.l	a5,a1
	CALLSYS	Remove
	
	;
	; device specific closings here...
	;

	;------ free our memory
	CLEAR	d0
	CLEAR	d1
	move.l	a5,a1
	move.w	LIB_NEGSIZE(a5),d1

	sub.w	d1,a1
	add.w	LIB_POSSIZE(a5),d0
	add.l	d1,d0

	CALLSYS	FreeMem

	;------ set up our return value
	move.l	d2,d0

Expunge_End:
	movem.l	(sp)+,d1/d2/a5/a6
	rts


Null:
	PUTMSG	30,<'%s/Null: called'>
	CLEAR	d0
	rts


InitUnit:	; ( d2:unit number, a3:scratch, a6:devptr )
	PUTMSG	30,<'%s/InitUnit: called'>
	movem.l	d2-d4/a2,-(sp)

	;------ allocate unit memory
	move.l	#MyDevUnit_Sizeof,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	LINKSYS	AllocMem,md_SysLib(a6)

	tst.l	d0
	beq	InitUnit_End

	move.l	d0,a3
	move.b	d2,mdu_UnitNum(a3)	; initialize unit number
	move.l	a6,mdu_Device(a3)	; initialize device pointer

	;------ start up the unit process.  We do a trick here --
	;------ we set his message port to PA_IGNORE until the
	;------ new process has a change to set it up.
	;------ We cannot go to sleep here: it would be very nasty
	;------ if someone else tried to open the unit
	;------ (exec's OpenDevice has done a Forbid() for us --
	;------ we depend on this to become single threaded).

	;------ Initialize the stack information
	lea	mdu_stack(a3),a0	; Low end of stack
	move.l	a0,mdu_tcb+TC_SPLOWER(a3)
	lea	MYPROCSTACKSIZE(a0),a0	; High end of stack
	move.l	a0,mdu_tcb+TC_SPUPPER(a3)
	move.l	a3,-(A0)		; argument -- unit ptr
	move.l	a0,mdu_tcb+TC_SPREG(a3)
	;------ initialize the unit's list
	lea	MP_MSGLIST(a3),a0
	NEWLIST	a0
	lea	mdu_tcb(a3),a0
	move.l	a0,MP_SIGTASK(a3)
	moveq.l	#0,d0			; Don't need to re-zero it
	move.l	a3,a2			; InitStruct is initializing the UNIT
	lea.l	mdu_Init,A1
	LINKSYS	InitStruct,md_SysLib(a6)

	move.l	a3,mdu_is+IS_DATA(a3)	; Pass int. server unit addr.

;	Startup the task
	lea	mdu_tcb(a3),a1
	lea	Proc_Begin(PC),a2
	move.l	a3,-(sp)		; Preserve UNIT pointer
	lea	-1,a3			; generate address error
					; if task ever "returns"
	CLEAR	d0
	LINKSYS AddTask,md_SysLib(a6)
	move.l	(sp)+,a3		; restore UNIT pointer

	;------ mark us as ready to go
	move.l	d2,d0			; unit number
	lsl.l	#2,d0
	move.l	a3,md_Units(a6,d0.l)	; set unit table


InitUnit_End:
	movem.l	(sp)+,d2-d4/a2
	rts

	;------ got an error.  free the unit structure that we allocated.
InitUnit_FreeUnit:
	bsr	FreeUnit
	bra.s	InitUnit_End

FreeUnit:	; ( a3:unitptr, a6:deviceptr )
	move.l	a3,a1
	move.l	#MyDevUnit_Sizeof,d0
	LINKSYS	FreeMem,md_SysLib(a6)
	rts


ExpungeUnit:	; ( a3:unitptr, a6:deviceptr )
	PUTMSG	30,<'%s/ExpungeUnit: called'>
	move.l	d2,-(sp)

;
; If you can expunge you unit, and each unit has it's own interrups,
; you must remember to remove its interrupt server
;

	IFD	INTRRUPT
	lea.l	mdu_is(a3),a1		; Point to interrupt structure
	moveq	#3,d0			; Portia interrupt bit 3
	LINKSYS	RemIntServer,md_SysLib(a6) ;Now remove the interrupt server
	ENDC

	;------ get rid of the unit's task.  We know this is safe
	;------ because the unit has an open count of zero, so it
	;------ is 'guaranteed' not in use.
	lea	mdu_tcb(a3),a1
	LINKSYS	RemTask,md_SysLib(a6)

	;------ save the unit number
	CLEAR	d2
	move.b	mdu_UnitNum(a3),d2

	;------ free the unit structure.
	bsr	FreeUnit

	;------ clear out the unit vector in the device
	lsl.l	#2,d2
	clr.l	md_Units(a6,d2.l)

	move.l	(sp)+,d2

	rts

;----------------------------------------------------------------------
;
; here begins the device specific functions
;
;----------------------------------------------------------------------

; cmdtable is used to look up the address of a routine that will
; implement the device command.
cmdtable:
	DC.L	Invalid		; $00000001
	DC.L	MyReset		; $00000002
	DC.L	RdWrt		; $00000004	Common routine for read/write
	DC.L	RdWrt		; $00000008
	DC.L	Update		; $00000010
	DC.L	Clear		; $00000020
	DC.L	MyStop		; $00000040
	DC.L	Start		; $00000080
	DC.L	Flush		; $00000100
	DC.L	Motor		; $00000200  motor	(NO-OP)
	DC.L	Seek		; $00000400  seek	(NO-OP)
	DC.L	RdWrt		; $00000800  format -> WRITE for RAMDISK
	DC.L	MyRemove	; $00001000  remove		(NO-OP)
	DC.L	ChangeNum	; $00002000  changenum		(Returns 0)
	DC.L	ChangeState	; $00004000  changestate	(Returns 0)
	DC.L	ProtStatus	; $00008000  protstatus		(Returns 0)
	DC.L	RawRead		; Not supported	(INVALID)
	DC.L	RawWrite	; Not supported	(INVALID)
	DC.L	GetDriveType	; Get drive type	(Returns 1)
	DC.L	GetNumTracks	; Get number of tracks (Returns NUMTRKS)
	DC.L	AddChangeInt	; Add disk change interrupt (NO-OP)
	DC.L	RemChangeInt	; Remove disk change interrupt ( NO-OP)
cmdtable_end:

; this define is used to tell which commands should not be queued
; command zero is bit zero.
; The immediate commands are Invalid, Reset, Stop, Start, Flush
IMMEDIATES	EQU	$000001c3

; These commands can NEVER be done "immediately" if using interrupts,
; since they would "wait" for the interrupt forever!
; Read, Write, Format
NEVERIMMED	EQU	$0000080C
;
; BeginIO starts all incoming io.  The IO is either queued up for the
; unit task or processed immediately.
;

BeginIO:	; ( iob: a1, device:a6 )
	PUTMSG	30,<'%s/BeginIO: called'>
	movem.l	d1/a0/a3,-(sp)

	;------ bookkeeping
	move.l	IO_UNIT(a1),a3

	;------ see if the io command is within range
	move.w	IO_COMMAND(a1),d0
	cmp.w	#MYDEV_END,d0
	bcc	BeginIO_NoCmd

	DISABLE	a0

	;------ process all immediate commands no matter what
	move.w	#IMMEDIATES,d1
	btst	d0,d1
	bne.s	BeginIO_Immediate

	IFD	INTRRUPT	; if using interrupts,
	;------ queue all NEVERIMMED commands no matter what
	move.w	#NEVERIMMED,d1
	btst	d0,d1
	bne.s	BeginIO_QueueMsg
	ENDC

	;------ see if the unit is STOPPED.  If so, queue the msg.
	btst	#MDUB_STOPPED,UNIT_FLAGS(a3)
	bne.s	BeginIO_QueueMsg

	;------ this is not an immediate command.  see if the device is
	;------ busy.
	bset	#UNITB_ACTIVE,UNIT_FLAGS(a3)
	beq.s	BeginIO_Immediate

	;------ we need to queue the device.  mark us as needing
	;------ task attention.  Clear the quick flag
BeginIO_QueueMsg:
	BSET	#UNITB_INTASK,UNIT_FLAGS(a3)
	bclr	#IOB_QUICK,IO_FLAGS(a1)

	ENABLE	a0

	move.l	a3,a0
	LINKSYS	PutMsg,md_SysLib(a6)
	bra	BeginIO_End

BeginIO_Immediate:
	ENABLE	a0

	bsr	PerformIO

BeginIO_End:
	movem.l	(sp)+,d1/a0/a3
	rts

BeginIO_NoCmd:
	move.b	#IOERR_NOCMD,IO_ERROR(a1)
	bra.s	BeginIO_End


;
; PerformIO actually dispatches an io request.  It expects a3 to already
; have the unit pointer in it.  a6 has the device pointer (as always).
; a1 has the io request.  Bounds checking has already been done on
; the io request.
;

PerformIO:	; ( iob:a1, unitptr:a3, devptr:a6 )
	PUTMSG	30,<'%s/PerforIO: called'>
	move.l	a2,-(sp)
	move.l	a1,a2

	clr.b	IO_ERROR(A2)		; No error so far
	move.w	IO_COMMAND(a2),d0
	lsl	#2,d0			; Multiply by 4 to get table offset
	lea	cmdtable(pc),a0
	move.l	0(a0,d0.w),a0

	jsr	(a0)

	move.l	(sp)+,a2
	rts

;
; TermIO sends the IO request back to the user.  It knows not to mark
; the device as inactive if this was an immediate request or if the
; request was started from the server task.
;

TermIO:		; ( iob:a1, unitptr:a3, devptr:a6 )
	PUTMSG	30,<'%s/TermIO: called'>
	move.w	IO_COMMAND(a1),d0
	move.w	#IMMEDIATES,d1
	btst	d0,d1
	bne.s	TermIO_Immediate

	;------ we may need to turn the active bit off.
	btst	#UNITB_INTASK,UNIT_FLAGS(a3)
	bne.s	TermIO_Immediate

	;------ the task does not have more work to do
	bclr	#UNITB_ACTIVE,UNIT_FLAGS(a3)

TermIO_Immediate:
	;------ if the quick bit is still set then we don't need to reply
	;------ msg -- just return to the user.
	btst	#IOB_QUICK,IO_FLAGS(a1)
	bne.s	TermIO_End

	LINKSYS	ReplyMsg,md_SysLib(a6)

TermIO_End:
	rts
	

AbortIO:	; ( iob: a1, device:a6 )
;----------------------------------------------------------------------
;
; here begins the functions that implement the device commands
; all functions are called with:
;	a1 -- a pointer to the io request block
;	a2 -- another pointer to the iob
;	a3 -- a pointer to the unit
;	a6 -- a pointer to the device
;
; Commands that conflict with 68000 instructions have a "My" prepended
; to them.
;----------------------------------------------------------------------

RawRead:		; 10 Not supported	(INVALID)
RawWrite:		; 11 Not supported	(INVALID)
Invalid:
	move.b	#IOERR_NOCMD,IO_ERROR(a1)
	bsr	TermIO
	rts

MyReset:
AddChangeInt:
RemChangeInt:
MyRemove:
Seek:
Motor:
ChangeNum:
ChangeState:
ProtStatus:
	clr.l	IO_ACTUAL(a1)	; Indicate drive isn't protected
	bsr	TermIO
	rts

GetDriveType:
	move.l	#1,IO_ACTUAL(a1)		; Make it look like 3.5"
	bsr	TermIO
	rts

GetNumTracks:
	move.l	#RAMSIZE/5120,IO_ACTUAL(a1)	; Number of 10 sector tracks
	bsr	TermIO
	rts

RdWrt:
	movem.l a2/a3,-(sp)
	clr.l	IO_ACTUAL(a1)		; Initially, no data moved
	move.l	IO_DATA(a1),a0
	move.l	IO_LENGTH(a1),d0

	;------ deal with zero length I/O
	beq.s	RdWrt_end


	move.l	IO_UNIT(a1),a3		; Get unit pointer
	move.l	a1,a2

*		check operation for legality

	move.l	IO_OFFSET(a2),d0
	move.l	d0,d1

*		check for being an even sector boundary
	and.l	#SECTOR-1,d1
	bne	IO_Err

*		check for IO within disc range
	add.l	IO_LENGTH(a2),d0
	cmp.l	#RAMSIZE,d0
	bgt	IO_Err

* We've gotten this far, it must be a valid request.

	IFD	INTRRUPT
	move.l	mdu_SigMask(a3),d0	; Get signals to wait for
	LINKSYS	Wait,md_SysLib(a6)	; Wait for interrrupt before proceeding
	ENDC

	move.l	IO_OFFSET(a2),d0
	lea	mdu_RAM(a3),a0		; Point to RAMDISK "sector" for I/O
	add.l	d0,a0			; Can't use index, "out of range"
	move.l	IO_LENGTH(a2),d0
	move.l	d0,IO_ACTUAL(a2)	; Indicate we've moved all bytes
	subq.w	#1,d0			; Adjust byte count for DBRA loop
	move.l	IO_DATA(a2),a1		; Point to data buffer

	move.w	IO_COMMAND(a2),d1	; Now go to correct loop for
	cmp.b	#CMD_READ,d1		; Read or Write commands
	BEQ.S	RdLp

WrtLp:	move.b	(a1)+,(a0)+		; Copy a byte
	dbra	d0,WrtLp		; Move all requested bytes
	bra.s	RdWrt_end

RdLp:	move.b	(a0)+,(a1)+		; Copy a byte
	dbra	d0,RdLp			; Move all requested bytes
	bra.s	RdWrt_end

IO_Err:
	move.b	#IOERR_BADLENGTH,IO_ERROR(a1)

RdWrt_end:
	move.l	a2,a1
	bsr	TermIO
	movem.l (sp)+,a2/a3
	rts

;
; Update and Clear are internal buffering commands.  Update forces all
; io out to its final resting spot, and does not return until this is
; done.  Clear invalidates all internal buffers.  Since this device
; has no internal buffers, these commands do not apply.
;

Update:
	PUTMSG	30,<'%s/Update: called'>
	bra	Invalid
Clear:
	PUTMSG	30,<'%s/Clear: called'>
	bra	Invalid

;
; the Stop command stop all future io requests from being
; processed until a Start command is received.  The Stop
; command is NOT stackable: e.g. no matter how many stops
; have been issued, it only takes one Start to restart
; processing.
;

MyStop:
	PUTMSG	30,<'%s/MyStop: called'>
	bset	#MDUB_STOPPED,UNIT_FLAGS(a3)

	bsr	TermIO
	rts
	
Start:
	PUTMSG	30,<'%s/Start: called'>
	bsr	InternalStart

	move.l	a2,a1
	bsr	TermIO

	rts

InternalStart:
	;------ turn processing back on
	bclr	#MDUB_STOPPED,UNIT_FLAGS(a3)

	;------ kick the task to start it moving
	move.l	a3,a1
	CLEAR	d0
	move.l	MP_SIGBIT(a3),d1
	bset	d1,d0
	LINKSYS	Signal,md_SysLib(a3)

	rts

;
; Flush pulls all io requests off the queue and sends them back.
; We must be careful not to destroy work in progress, and also
; that we do not let some io requests slip by.
;
; Some funny magic goes on with the STOPPED bit in here.  Stop is
; defined as not being reentrant.  We therefore save the old state
; of the bit and then restore it later.  This keeps us from
; needing to DISABLE in flush.  It also fails miserably if someone
; does a start in the middle of a flush.
;

Flush:
	PUTMSG	30,<'%s/Flush: called'>
	movem.l	d2/a6,-(sp)

	move.l	md_SysLib(a6),a6

	bset	#MDUB_STOPPED,UNIT_FLAGS(a3)
	sne	d2

Flush_Loop:
	move.l	a3,a0
	CALLSYS	GetMsg

	tst.l	d0
	beq.s	Flush_End

	move.l	d0,a1
	move.b	#IOERR_ABORTED,IO_ERROR(a1)
	CALLSYS	ReplyMsg

	bra.s	Flush_Loop

Flush_End:

	move.l	d2,d0
	movem.l	(sp)+,d2/a6

	tst.b	d0
	beq.s	1$

	bsr	InternalStart
1$:

	move.l	a2,a1
	bsr	TermIO

	rts

;
; Foo and Bar are two device specific commands that are provided just
; to show you how to add your own commands.  The currently return that
; no work was done.
;

Foo:
Bar:
	CLEAR	d0
	move.l	d0,IO_ACTUAL(a1)

	bsr	TermIO
	rts

;----------------------------------------------------------------------
;
; here begins the process related routines
;
; A Process is provided so that queued requests may be processed at
; a later time.
;
;
; Register Usage
; ==============
; a3 -- unit pointer
; a6 -- syslib pointer
; a5 -- device pointer
; a4 -- task (NOT process) pointer
; d7 -- wait mask
;
;----------------------------------------------------------------------

; some dos magic.  A process is started at the first executable address
; after a segment list.  We hand craft a segment list here.  See the
; the DOS technical reference if you really need to know more about this.

	cnop	0,4			; long word allign
	DC.L	16			; segment length -- any number will do
myproc_seglist:
	DC.L	0			; pointer to next segment

; the next instruction after the segment list is the first executable address

Proc_Begin:

	move.l	_AbsExecBase,a6

	;------ Grab the argument
	move.l	4(sp),a3		; Unit pointer

	move.l	mdu_Device(a3),a5	; Point to device structure

	IFD	INTRRUPT
	;------ Allocate a signal for "I/O Complete" interrupts
	moveq	#-1,d0			; -1 is any signal at all
	CALLSYS	AllocSignal
	move.b	d0,mdu_SigBit(A3)	; Save in unit structure

	moveq	#0,d7			; Convert bit number signal mask
	bset	d0,d7
	move.l	d7,mdu_SigMask(A3)	; Save in unit structure

	lea.l	mdu_is(a3),a1		; Point to interrupt structure
	moveq	#3,d0			; Portia interrupt bit 3
	CALLSYS AddIntServer		; Now install the server

	move.l	md_Base(a5),a0		; Get board base address
*	bset.b	#INTENABLE,INTCTRL2(a0)	; Enable interrupts
	ENDC

	;------ Allocate the right signal

	moveq	#-1,d0			; -1 is any signal at all
	CALLSYS	AllocSignal

	move.b	d0,MP_SIGBIT(a3)
	move.b	#PA_SIGNAL,MP_FLAGS(a3)

	;------ change the bit number into a mask, and save in d7

	moveq	#0,d7
	bset	d0,d7

	;------
	;------ OK, kids, we are done with initialization.  We now
	;------ can start the main loop of the driver.  It goes
	;------ like this.  Because we had the port marked PA_IGNORE
	;------ for a while (in InitUnit) we jump to the getmsg
	;------ code on entry.
	;------
	;------		wait for a message
	;------		lock the device
	;------		get a message.  if no message unlock device and loop
	;------		dispatch the message
	;------		loop back to get a message
	;------

	bra.s	Proc_CheckStatus

	;------ main loop: wait for a new message
Proc_MainLoop:
	move.l	d7,d0
	CALLSYS	Wait

Proc_CheckStatus:
	;------ see if we are stopped
	btst	#MDUB_STOPPED,UNIT_FLAGS(a3)
	bne.s	Proc_MainLoop		; device is stopped

	;------ lock the device
	bset	#UNITB_ACTIVE,UNIT_FLAGS(a3)
	bne.s	Proc_MainLoop		; device in use

	;------ get the next request
Proc_NextMessage:
	move.l	a3,a0
	CALLSYS	GetMsg
	tst.l	d0
	beq.s	Proc_Unlock		; no message?

	;------ do this request
	move.l	d0,a1
	exg	a5,a6			; put device ptr in right place
	bsr	PerformIO
	exg	a5,a6			; get syslib back in a6

	bra.s	Proc_NextMessage

	;------ no more messages.  back ourselves out.
Proc_Unlock:
	and.b	#$ff&(~(UNITF_ACTIVE!UNITF_INTASK)),UNIT_FLAGS(a3)
	bra	Proc_MainLoop

;
; Here is a dummy interrupt handler, with some crucial components commented
; out.  If the IFD INTRRUPT is enabled, this code will cause the device to
; wait for a level two interrupt before it will process each request
; (pressing a key on the keyboard will do it).  This code is normally
; disabled, and must fake or omit certain operations since there  isn't
; really any hardware for this driver.  Similiar code has been used
; successfully in other, "REAL" device drivers.
;

	IFD	INTRRUPT
;	A1 should be pointing to the unit structure upon entry!

myintr:		move.l	mdu_Device(a1),a0	; Get device pointer
		move.l	md_SysLib(a0),a6	; Get pointer to system
		move.l	md_Base(a0),a0		; point to board base address
*		btst.b	#IAMPULLING,INTCTRL1(a0);See if I'm interrupting
*		beq.s	myexnm			; if not set, exit, not mine
*		move.b	#0,INTACK(a0)		; toggle controller's int2 bit

;		------ signal the task that an interrupt has occured

		move.l	mdu_SigMask(a1),d0
		lea	mdu_tcb(a1),a1
		CALLSYS	Signal

;
;		now clear the zero condition code so that
;		the interrupt handler doesn't call the next
;		interrupt server.
;
*		moveq	#1,d0			clear zero flag
*		bra.s	myexit			now exit
;
;		this exit point sets the zero condition code
;		so the interrupt handler will try the next server
;		in the interrupt chain
;
myexnm		moveq	#0,d0			set zero condition code
;
myexit		rts
	ENDC

mdu_Init:
;	------ Initialize the device

	INITBYTE	MP_FLAGS,PA_IGNORE
	INITBYTE	LN_TYPE,NT_DEVICE
	INITLONG	LN_NAME,myName
	INITBYTE	mdu_Msg+LN_TYPE,NT_MSGPORT;Unit starts with MsgPort
	INITLONG	mdu_Msg+LN_NAME,myName		
	INITLONG	mdu_tcb+LN_NAME,myName
	INITBYTE	mdu_tcb+LN_TYPE,NT_TASK
	INITBYTE	mdu_tcb+LN_PRI,5
	INITBYTE	mdu_is+LN_PRI,4		; Int priority 4
	IFD	INTRRUPT
	INITLONG	mdu_is+IS_CODE,myintr	; Interrupt routine addr
	ENDC
	INITLONG	mdu_is+LN_NAME,myName
	DC.L	0

mdn_Init:
*	;------ Initialize packet for MakeDosNode

	INITLONG	mdn_execName,myName	; Address of driver name
	INITLONG	mdn_tableSize,11	; # long words in AmigaDOS env.
	INITLONG	mdn_dName,$52414d00	; Store 'RAM' in name
	INITLONG	mdn_sizeBlock,128	; # longwords in a block
	INITLONG	mdn_numHeads,1		; RAM disk has only one "head"
	INITLONG	mdn_secsPerBlk,1	; secs/logical block, must = "1"
	INITLONG	mdn_blkTrack,10		; secs/track (must be reasonable)
	INITLONG	mdn_resBlks,1		; reserved blocks, MUST > 0!
	INITLONG	mdn_upperCyl,(RAMSIZE/5120)-1; upper cylinder
	INITLONG	mdn_numBuffers,1	; # AmigaDOS buffers to start
	DC.L	0

;----------------------------------------------------------------------
; EndCode is a marker that show the end of your code.
; Make sure it does not span sections nor is before the
; rom tag in memory!  It is ok to put it right after
; the rom tag -- that way you are always safe.  I put
; it here because it happens to be the "right" thing
; to do, and I know that it is safe in this case.
;----------------------------------------------------------------------
EndCode:

	END
