[Contents] [Index] [Help] [Retrace] [Browse <] [Browse >]

*   Copyright (C) 1986-1999 Amiga, Inc.  All rights reserved.
*   Permission granted for non-commercial use.
* ramdev.asm -- Skeleton device code.
* A sample 4 unit ramdisk that can be bound to an expansion slot device,
* or used without.  Works with the Fast File System.
* This code is required reading for device driver writers.  It contains
* information not found elsewhere.  This code is somewhat old; you probably
* don't want to copy it directly.
* This example includes a task, though a task is not actually needed for
* a simple ram disk.  Unlike a single set of hardware registers that
* may need to be shared by multiple tasks, ram can be freely shared.
* This example does not show arbitration of hardware resources.
* Tested with CAPE and Metacomco
*	   Based on mydev.asm
*	   10/07/86 Modified by Lee Erickson to be a simple disk device
*		    using RAM to simulate a disk.
*	   02/02/88 Modified by C. Scheppner, renamed ramdev
*	   09/28/88 Repaired by Bryce Nesbitt for new release
*	   11/02/88 More clarifications
*	   02/01/89 Even more clarifications & warnings
*	   02/22/89 START/STOP fix from Marco Papa
* Bugs: If RTF_AUTOINIT fails, library base still left in memory.

   SECTION firstsection

   include "exec/types.i"
   include "exec/devices.i"
   include "exec/initializers.i"
   include "exec/memory.i"
   include "exec/resident.i"
   include "exec/io.i"
   include "exec/ables.i"
   include "exec/errors.i"
   include "exec/tasks.i"
   include "hardware/intbits.i"

   include "asmsupp.i"  ;standard asmsupp.i, same as used for library
   include "ramdev.i"

   include "libraries/expansion.i"
   include "libraries/configvars.i"
   include "libraries/configregs.i"

ABSEXECBASE equ 4   ;Absolute location of the pointer to exec.library base

   ;------ 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

   ;Pull these _LVOs in from amiga.lib
   XLIB   AddIntServer
   XLIB   RemIntServer
   XLIB   Debug
   XLIB   InitStruct
   XLIB   OpenLibrary
   XLIB   CloseLibrary
   XLIB   Alert
   XLIB   FreeMem
   XLIB   Remove
   XLIB   AddPort
   XLIB   AllocMem
   XLIB   AddTask
   XLIB   PutMsg
   XLIB   RemTask
   XLIB   ReplyMsg
   XLIB   Signal
   XLIB   GetMsg
   XLIB   Wait
   XLIB   WaitPort
   XLIB   AllocSignal
   XLIB   SetTaskPri
   XLIB   GetCurrentBinding	;Use to get list of boards for this driver
   XLIB   MakeDosNode
   XLIB   AddDosNode
   XLIB   CopyMemQuick	;Highly optimized copy function from exec.library

   INT_ABLES	    ;Macro from exec/ables.i

; 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 device).

		moveq	#-1,d0

; A romtag structure.  After your driver is brought in from disk, the
; disk image will be scanned 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

     DC.L    initDDescrip	; APTR	RT_MATCHTAG  (Back pointer)
     DC.L    EndCode		; APTR	RT_ENDSKIP   (To end of this hunk)
     DC.B    RTF_AUTOINIT	; UBYTE RT_FLAGS     (magic-see "Init:")
     DC.B    NT_DEVICE		; UBYTE RT_TYPE      (must be correct)
     DC.L    myName		; APTR	RT_NAME      (exec name)
     DC.L    idString		; APTR	RT_IDSTRING  (text string)
     DC.L    Init		; APTR	RT_INIT
	       ; LABEL RT_SIZE

   ;This name for debugging use
   IFNE INFO_LEVEL  ;If any debugging enabled at all
    dc.b    "ramdev",0

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

ExLibName    dc.b 'expansion.library',0   ; Expansion Library Name

   ; a major version number.
VERSION:    EQU   37

   ; 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.

   ; this is an identifier tag to help in supporting the device
   ; format is 'name version.revision (d.m.yy)',<cr>,<lf>,<null>
idString:   dc.b   'ramdev 37.1 (28.8.91)',13,10,0

   ; force word alignment
   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.

   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

   ;------ standard system routines
   dc.l   Open
   dc.l   Close
   dc.l   Expunge
   dc.l   Null	    ;Reserved for future use!

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

   ;------ custom extended functions
   dc.l   FunctionA
   dc.l   FunctionB

   ;------ 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 macros 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
   DC.W   0   ;terminate list

;-------- initRoutine -------------------------------------------------------
;   This routine gets called after the device has been allocated.
;   The device pointer is in D0.  The AmigaDOS segment list is in a0.
;   If it returns the device pointer, then the device will be linked
;   into the device list.  If it returns NULL, then the device
;   will be unloaded.
;   If you don't use the "RTF_AUTOINIT" feature, there is an additional
;   caveat.  If you allocate memory in your Open function, remember that
;   allocating memory can cause an Expunge... including an expunge of your
;   device.  This must not be fatal.  The easy solution is don't add your
;   device to the list until after it is ready for action.
; This call is single-threaded by exec; please read the description for
; "Open" below.
; Register Usage
; ==============
; a3 -- Points to temporary RAM
; a4 -- Expansion library base
; a5 -- device pointer
; a6 -- Exec base
   ;------ get the device pointer into a convenient A register
   PUTMSG   5,<'%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)    ;faster access than move.l 4,a6

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

* Here starts the AutoConfig stuff.  If this driver was to be tied to
* an expansion board, you would put this driver in the expansion drawer,
* and be called when BindDrivers finds a board that matches this driver.
* The Amiga, Inc. assigned product number of your board must be
* specified in the "PRODUCT=" field in the TOOLTYPES of this driver's icon.
* GetCurrentBinding() returns your (first) board.
   lea.l     ExLibName,A1	; Get expansion lib. name
   moveq.l   #0,D0
   CALLSYS   OpenLibrary	; Open the expansion library
   tst.l     D0
   beq	     Init_Error

   ;------ init_OpSuccess:
   move.l    D0,A4	    ;[expansionbase to A4]
   moveq     #0,D3
   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

   PUTMSG    10,<'%s/Init: GetCurrentBinding returned non-zero'>
   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

; 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.
; The AddDosNode call adds things to dos's list without needing to
; use mount.  We'll mount all 4 of our units whenever we are
; started.

;!!! If your card was successfully configured, you can mount the
;!!! units as DOS nodes

   ;------   Allocate temporary 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	    ;:BUG: AllocMem error not checked here.

   ;-----   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     #0,d6	      ; Now tell AmigaDOS about all units UNITNUM
   move.b    d6,d0	     ; Get unit number
   add.b     #$30,d0	     ; Make ASCII, minus 1
   move.b    d0,mdn_dName+2(a3)   ;   and store in name
   move.l    d6,mdn_unit(a3)      ; Store unit # in environment

;! Before adding to the dos list, you should really check if you
;! are about to cause a name collision.  This example does not.

   move.l    a3,a0
   LINKLIB   _LVOMakeDosNode,a4   ; Build AmigaDOS structures
   ;This can fail, but so what?
   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     ; See note below
   ;It's ok to pass a zero in here
   LINKLIB   _LVOAddDosNode,a4

; ADNF_STARTPROC will work, but only if dn_SegList is filled in
; in the SegPtr of the handler task.

   addq     #1,d6	  ; Bump unit number
   cmp.b    #MD_NUMUNITS,d6
   bls.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 any particular manufacturer ID
*   installed when running this demo.

   move.l   a5,d0
   movem.l  (sp)+,d1-d7/a0-a5

; Here begins the system interface commands.  When the user calls
; OpenDevice/CloseDevice/RemDevice, 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.
;   These calls are guaranteed to be single-threaded; only one task
;   will execute your Open/Close/Expunge at a time.
;   For Kickstart V33/34, the single-threading method involves "Forbid".
;   There is a good chance this will change.  Anything inside your
;   Open/Close/Expunge that causes a direct or indirect Wait() will break
;   the Forbid().  If the Forbid() is broken, some other task might
;   manage to enter your Open/Close/Expunge code at the same time.
;   Take care!
; Since exec has turned off task switching while in these routines
; (via Forbid/Permit), we should not take too long in them.

   ; Open sets the IO_ERROR field on an error.	If it was successfull,
   ; we should also set up the IO_UNIT and LN_TYPE fields.
   ; exec takes care of setting up IO_DEVICE.

Open:	   ; ( device:a6, iob:a1, unitnum:d0, flags:d1 )

;** Subtle point: any AllocMem() call can cause a call to this device's
;** expunge vector.  If LIB_OPENCNT is zero, the device might get expunged.
   addq.w   #1,LIB_OPENCNT(a6)  ;Fake an opener for duration of call <|>

   PUTMSG   20,<'%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	*!* UNIT 0 to 3 *!*
   cmp.l   #MD_NUMUNITS,d0
   bcc.s   Open_Range_Error   ; unit number out of range (BHS)

   ;------ 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	;scratch:a3 unitnum:d2 devpoint:a6

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

   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)     ;Internal bookkeeping

   ;------ prevent delayed expunges
   bclr     #LIBB_DELEXP,md_Flags(a6)

   CLEAR    d0
   move.b   d0,IO_ERROR(a2)
   move.b   #NT_REPLYMSG,LN_TYPE(a2) ;IMPORTANT: Mark IORequest as "complete"


   subq.w   #1,LIB_OPENCNT(a6) ;** End of expunge protection <|>
   movem.l  (sp)+,d2/a2/a3/a4

   moveq    #IOERR_OPENFAIL,d0
   move.b   d0,IO_ERROR(a2)
   move.l   d0,IO_DEVICE(a2)    ;IMPORTANT: trash IO_DEVICE on open failure
   PUTMSG   2,<'%s/Open: failed'>
   bra.s    Open_End

; There are two different things that might be returned from the Close
; routine.  If the device wishes to be unloaded, then Close must return
; the segment list (as given to Init).  Otherwise close MUST return NULL.

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

   move.l   a1,a2

   move.l   IO_UNIT(a2),a3

   ;------ IMPORTANT: make sure the IORequest is not used again
   ;------ with a -1 in IO_DEVICE, any BeginIO() attempt will
   ;------ immediatly halt (which is better than a subtle corruption
   ;------ that will lead to hard-to-trace crashes!!!!!!!!!!!!!!!!!!
   moveq.l  #-1,d0
   move.l   d0,IO_UNIT(a2)      ;We're closed...
   move.l   d0,IO_DEVICE(a2)    ;customers not welcome at this IORequest!!

   ;------ 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

   CLEAR   d0
   ;------ mark us as having one fewer openers
   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

   movem.l   (sp)+,d1/a2-a3
   rts				;MUST return either zero or the SegList!!!

;------- Expunge -----------------------------------------------------------
; Expunge is called by the memory allocator when the system is low on
; memory.
; There are two different things that might be returned from the Expunge
; routine.  If the device is no longer open then Expunge may return the
; segment list (as given to Init).  Otherwise Expunge may 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.
;	A6	    - library base (scratch)
;	D0-D1/A0-A1 - scratch
Expunge:   ; ( device: a6 )
   PUTMSG   10,<'%s/Expunge: called'>

   movem.l  d1/d2/a5/a6,-(sp)   ; 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

   ;------ 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		;Remove first (before FreeMem)

   ; device specific closings here...

   ;------ free our memory (must calculate from LIB_POSSIZE & LIB_NEGSIZE)
   move.l   a5,a1		;Devicebase
   CLEAR    d0
   move.w   LIB_NEGSIZE(a5),d0
   suba.l   d0,a1		;Calculate base of functions
   add.w    LIB_POSSIZE(a5),d0  ;Calculate size of functions + data area
   CALLSYS  FreeMem

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

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

;------- Null ---------------------------------------------------------------
   PUTMSG  1,<'%s/Null: called'>
   CLEAR   d0
   rts	    ;The "Null" function MUST return NULL.

;------- Custom ------------------------------------------------------------
;Two "do nothing" device-specific functions
    add.l   d1,d0   ;Add
    add.l   d0,d0   ;Double


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

   moveq.l  #0,d0	   ; Don't need to re-zero it
   move.l   a3,a2	   ; InitStruct is initializing the UNIT
   lea.l    mdu_Init(pc),A1
   LINKSYS  InitStruct,md_SysLib(a6)

   ;!! IMPORTANT !!
   move.l   #42414400,mdu_RAM(a3)   ;Mark offset zero as ASCII "BAD "
   ;!! IMPORTANT !!

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

   ;------ start up the unit task.  We do a trick here --
   ;------ we set his message port to PA_IGNORE until the
   ;------ new task 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 (send on stack)
   move.l   a0,mdu_tcb+TC_SPREG(a3)
   lea	    mdu_tcb(a3),a0
   move.l   a0,MP_SIGTASK(a3)

       move.l	a0,-(SP)
       move.l	a3,-(SP)
       PUTMSG	30,<'%s/InitUnit, unit= %lx, task=%lx'>
       addq.l	#8,sp

   ;------ initialize the unit's message port's list
   lea	    MP_MSGLIST(a3),a0
   NEWLIST  a0			;<- IMPORTANT! Lists MUST! have NEWLIST
				;work magic on them before use.  (AddPort()
				;can do this for you)

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

;   Startup the task
   lea	    mdu_tcb(a3),a1
   lea	    Task_Begin(PC),a2
   move.l   a3,-(sp)      ; Preserve UNIT pointer
   lea	    -1,a3	  ; generate address error
			  ; if task ever "returns" (we RemTask() it
			  ; to get rid of it...)
   CLEAR   d0
   PUTMSG   30,<'%s/About to add task'>
   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
   PUTMSG   30,<'%s/InitUnit: ok'>

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

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

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

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

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

   ;------ 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

; here begins the device functions
; cmdtable is used to look up the address of a routine that will
; implement the device command.
; NOTE: the "extended" commands (ETD_READ/ETD_WRITE) have bit 15 set!
; We deliberately refuse to operate on such commands.  However a driver
; that supports removable media may want to implement this.  One
; open issue is the handling of the "seclabel" area. It is probably
; best to reject any command with a non-null "seclabel" pointer.
   DC.L   Invalid	;$00000001  ;0	CMD_INVALID
   DC.L   MyReset	;$00000002  ;1	CMD_RESET
   DC.L   RdWrt 	;$00000004  ;2	CMD_READ	(\/common)
   DC.L   RdWrt 	;$00000008  ;3	CMD_WRITE	(/\common)  ETD_
   DC.L   Update	;$00000010  ;4	CMD_UPDATE	(NO-OP)     ETD_
   DC.L   Clear 	;$00000020  ;5	CMD_CLEAR	(NO-OP)     ETD_
   DC.L   MyStop	;$00000040  ;6	CMD_STOP		    ETD_
   DC.L   Start 	;$00000080  ;7	CMD_START
   DC.L   Flush 	;$00000100  ;8	CMD_FLUSH
   DC.L   Motor 	;$00000200  ;9	TD_MOTOR	(NO-OP)     ETD_
   DC.L   Seek		;$00000400  ;A	TD_SEEK 	(NO-OP)     ETD_
   DC.L   RdWrt 	;$00000800  ;B	TD_FORMAT	(Same as write)
   DC.L   MyRemove	;$00001000  ;C	TD_REMOVE	(NO-OP)
   DC.L   ChangeNum	;$00002000  ;D	TD_CHANGENUM	(returns 0)
   DC.L   ChangeState	;$00004000  ;E	TD_CHANGESTATE	(returns 0)
   DC.L   ProtStatus	;$00008000  ;F	TD_PROTSTATUS	(returns 0)
   DC.L   RawRead	;$00010000  ;10 TD_RAWREAD	(INVALID)
   DC.L   RawWrite	;$00020000  ;11 TD_RAWWRITE	(INVALID)
   DC.L   GetDriveType	;$00040000  ;12 TD_GETDRIVETYPE (Returns 1)
   DC.L   GetNumTracks	;$00080000  ;13 TD_GETNUMTRACKS (Returns NUMTRKS)
   DC.L   AddChangeInt	;$00100000  ;14 TD_ADDCHANGEINT (NO-OP)
   DC.L   RemChangeInt	;$00200000  ;15 TD_REMCHANGEINT (NO-OP)

; this define is used to tell which commands should be handled
; immediately (on the caller's schedule).
; The immediate commands are Invalid, Reset, Stop, Start, Flush
; Note that this method limits you to just 32 device specific commands,
; which may not be enough.
;IMMEDIATES   EQU   %00000000000000000000000111000011
;;		     --------========--------========
;;		     FEDCBA9876543210FEDCBA9876543210

;;An alternate version.  All commands that are trivially short
;;and %100 reentrant are included.  This way you won't get the
;;task switch overhead for these commands.
IMMEDIATES   EQU   %11111111111111111111011111110011
;		    --------========--------========
;		    FEDCBA9876543210FEDCBA9876543210

    IFD   INTRRUPT   ; if using interrupts,
; 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 often is given the responsibility of making devices single
; threaded... so two tasks sending commands at the same time don't cause
; a problem.  Once this has been done, the command is dispatched via
; PerformIO.
; There are many ways to do the threading.  This example uses the
; UNITB_ACTIVE bit.  Be sure this is good enough for your device before
; using!  Any method is ok.  If immediate access can not be obtained, the
; request is queued for later processing.
; Some IO requests do not need single threading, these can be performed
; immediatley.
;   The exec WaitIO() function uses the IORequest node type (LN_TYPE)
;   as a flag.	If set to NT_MESSAGE, it assumes the request is
;   still pending and will wait.  If set to NT_REPLYMSG, it assumes the
;   request is finished.  It's the responsibility of the device driver
;   to set the node type to NT_MESSAGE before returning to the user.
BeginIO:   ; ( iob: a1, device:a6 )

	bchg.b	#1,$bfe001  ;Blink the power LED
     clr.l    -(sp)
     move.w   IO_COMMAND(a1),2(sp)  ;Get entire word
     PUTMSG   3,<'%s/BeginIO  -- $%lx'>
     addq.l   #4,sp

    movem.l   d1/a0/a3,-(sp)

    move.b  #NT_MESSAGE,LN_TYPE(a1) ;So WaitIO() is guaranteed to work
    move.l  IO_UNIT(a1),a3          ;bookkeeping -> what unit to play with
    move.w  IO_COMMAND(a1),d0

    ;Do a range check & make sure ETD_XXX type requests are rejected
    cmp.w   #MYDEV_END,d0	;Compare all 16 bits
    bcc     BeginIO_NoCmd	;no, reject it.  (bcc=bhs - unsigned)

    ;------ process all immediate commands no matter what
    move.l  #IMMEDIATES,d1
    DISABLE a0			;<-- Ick, nasty stuff, but needed here.
    btst.l  d0,d1
    bne     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

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

    ;------ This is not an immediate command.  See if the device is
    ;------ busy.  If the device is not, do the command on the
    ;------ user schedule.  Else fire up the task.
    ;------ This type of arbitration is not really needed for a ram
    ;------ disk, but is essential for a device to reliably work
    ;------ with shared hardware
    ;------ When the lines below are ";" commented out, the task gets
    ;------ a better workout.  When the lines are active, the calling
    ;------ process is usually used for the operation.
    ;------ REMEMBER:::: Never Wait() on the user's schedule in BeginIO()!
    ;------ The only exception is when the user has indicated it is ok
    ;------ by setting the "quick" bit.  Since this device copies from
    ;------ ram that never needs to be waited for, this subtlely may not
    ;------ be clear.
    bset    #UNITB_ACTIVE,UNIT_FLAGS(a3)   ;<---- comment out these
    beq.s   BeginIO_Immediate		   ;<---- lines to test task.

    ;------ we need to queue the device.  mark us as needing
    ;------ task attention.  Clear the quick flag
    bset    #UNITB_INTASK,UNIT_FLAGS(a3)
    bclr    #IOB_QUICK,IO_FLAGS(a1)   ;We did NOT complete this quickly
    ENABLE  a0

     move.l  a1,-(sp)
     move.l  a3,-(sp)
     PUTMSG  250,<'%s/PutMsg: Port=%lx Message=%lx'>
     addq.l  #8,sp

    move.l  a3,a0
    LINKSYS  PutMsg,md_SysLib(a6)   ;Port=a0, Message=a1
    bra.s   BeginIO_End
    ;----- return to caller before completing

    ;------ Do it on the schedule of the calling process
    ENABLE  a0
    bsr.s   PerformIO

    PUTMSG  200,<'%s/BeginIO_End'>
    movem.l (sp)+,d1/a0/a3

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

; PerformIO actually dispatches an io request.	It might be called from
; the task, or directly from BeginIO (thus on the callers's schedule)
; 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 I/O Request.

PerformIO:   ; ( iob:a1, unitptr:a3, devptr:a6 )
     clr.l    -(sp)
     move.w   IO_COMMAND(a1),2(sp)  ;Get entire word
     PUTMSG   150,<'%s/PerformIO -- $%lx'>
     addq.l   #4,sp

    moveq   #0,d0
    move.b  d0,IO_ERROR(A1)     ; No error so far
    move.b  IO_COMMAND+1(a1),d0 ;Look only at low byte
    lsl.w   #2,d0		; Multiply by 4 to get table offset
    lea.l   cmdtable(pc),a0
    move.l  0(a0,d0.w),a0

    jmp     (a0)    ;iob:a1  unit:a3  devprt:a6

; 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  160,<'%s/TermIO'>
    move.w  IO_COMMAND(a1),d0

    move.w  #IMMEDIATES,d1
    btst    d0,d1
    bne.s   TermIO_Immediate	;IO was immediate, don't do task stuff...

    ;------ we may need to turn the active bit off.
    btst    #UNITB_INTASK,UNIT_FLAGS(a3)
    bne.s   TermIO_Immediate	;IO was came from task, don't clear ACTIVE...

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

    ;------ 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)      ;a1-message
    ;(ReplyMsg sets the LN_TYPE to NT_REPLYMSG)


; Here begins the functions that implement the device commands
; all functions are called with:
;   a1 -- a pointer to the io request block
;   a3 -- a pointer to the unit
;   a6 -- a pointer to the device
; Commands that conflict with 68000 instructions have a "My" prepended
; to them.

;We can't AbortIO anything, so don't touch the IORequest!
;AbortIO() is a REQUEST to "hurry up" processing of an IORequest.
;If the IORequest was already complete, nothing happens (if an IORequest
;is quick or LN_TYPE=NT_REPLYMSG, the IORequest is complete).
;The message must be replied with ReplyMsg(), as normal.
AbortIO:	; ( iob: a1, device:a6 )
    moveq   #IOERR_NOCMD,d0 ;return "AbortIO() request failed"

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

; Update and Clear are internal buffering commands.  Update forces all
; io out to its final resting spot, and does not return until this is
; totally done.  Since this is automatic in a ramdisk, we simply return "Ok".
; Clear invalidates all internal buffers.  Since this device
; has no internal buffers, these commands do not apply.
MyReset:		    ;Do nothing (nothing reasonable to do)
AddChangeInt:		    ;Do nothing
RemChangeInt:		    ;Do nothing
MyRemove:		    ;Do nothing
Seek:			    ;Do nothing
Motor:			    ;Do nothing
ChangeNum:		    ;Return zero (changecount =0)
ChangeState:		    ;Zero indicates disk inserted
ProtStatus:		    ;Zero indicates unprotected
    clr.l   IO_ACTUAL(a1)
    bra.s   TermIO

GetDriveType:		    ;make it look like 3.5" (90mm) drive
    moveq   #DRIVE3_5,d0
    move.l  d0,IO_ACTUAL(a1)
    bra.s   TermIO

    move.l  #RAMSIZE/BYTESPERTRACK,IO_ACTUAL(a1) ;Number of tracks
    bra.s   TermIO

; Foo and Bar are two device specific commands that are provided just
; to show you how commands are added.  They currently return that
; no work was done.
    clr.l   IO_ACTUAL(a1)
    bra     TermIO

; This device is designed so that no combination of bad
; inputs can ever cause the device driver to crash.
	move.l	IO_DATA(a1),-(sp)
	move.l	IO_OFFSET(a1),-(sp)
	move.l	IO_LENGTH(a1),-(sp)
	PUTMSG	200,<'%s/RdWrt len %ld offset %ld data $%lx'>
	addq.l	#8,sp
	addq.l	#4,sp

    movem.l a2/a3,-(sp)
    move.l  a1,a2		;Copy iob
    move.l  IO_UNIT(a2),a3      ;Get unit pointer

*      check operation for legality
    btst.b  #0,IO_DATA+3(a2)    ;check if user's pointer is ODD
    bne.s   IO_LenErr		;bad...

    move.l  IO_OFFSET(a2),d0
    move.l  d0,d1
    and.l   #SECTOR-1,d1	;Bad sector boundary or alignment?
    bne.s   IO_LenErr		;bad...

*      check for IO within disc range
    add.l   IO_LENGTH(a2),d0    ;Add length to offset
    bcs.s   IO_LenErr		;overflow... (important test)
    cmp.l   #RAMSIZE,d0 	;Last byte is highest acceptable total
    bhi.s   IO_LenErr		;bad... (unsigned compare)
    and.l   #SECTOR-1,d0	;Even sector boundary?
    bne.s   IO_LenErr		;bad...

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

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

    lea.l   mdu_RAM(a3),a0      ; Point to RAMDISK "sector" for I/O
    add.l   IO_OFFSET(a2),a0    ; Add offset to ram base
    move.l  IO_LENGTH(a2),d0
    move.l  d0,IO_ACTUAL(a2)    ; Indicate we've moved all bytes
    beq.s   RdWrt_end		;---deal with zero length I/O
    move.l  IO_DATA(a2),a1      ; Point to data buffer
;A0=ramdisk index
;A1=user buffer
    cmp.b   #CMD_READ,IO_COMMAND+1(a2)  ; Decide on direction
    BEQ.S   CopyTheBlock
    EXG     A0,A1		; For Write and Format, swap source & dest
    LINKSYS CopyMemQuick,md_SysLib(a6)  ;A0=source A1=dest D0=size
    ;CopyMemQuick is very fast

    move.l  a2,a1
    movem.l (sp)+,a2/a3
    bra     TermIO	;END

    PUTMSG  10,<'bad length'>
    clr.l   IO_ACTUAL(a2)       ;Initially, no data moved
    bra.s   RdWrt_end

; 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.
;Stop is rather silly for a ramdisk
   PUTMSG   30,<'%s/MyStop: called'>
   bra	 TermIO

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

	   ;[A3=unit A6=device]
    move.l  a1,-(sp)
    ;------ turn processing back on
    ;------ kick the task to start it moving
    move.b  MP_SIGBIT(a3),d1
    CLEAR   d0
    bset    d1,d0		    ;prepared signal mask
    move.l  MP_SIGTASK(a3),a1       ;:FIXED:marco-task to signal
    LINKSYS Signal,md_SysLib(a6)    ;:FIXED:marco-a6 not a3
    move.l  (sp)+,a1

; Flush pulls all I/O 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. (A semaphore might help...)

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

   move.l   md_SysLib(a6),a6

   sne	 d2

   move.l   a3,a0
   CALLSYS   GetMsg	;Steal messages from task's port

   tst.l   d0
   beq.s   Flush_End

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

   bra.s   Flush_Loop

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

   tst.b   d0
   beq.s   1$

   bsr	 InternalStart
   bra	   TermIO

; Here begins the task related routines
; A Task is provided so that queued requests may be processed at
; a later time.  This is not very justifiable for a ram disk, but
; is very useful for "real" hardware devices.  Take care with
; your arbitration of shared hardware with all the multitasking
; programs that might call you at once.
; Register Usage
; ==============
; a3 -- unit pointer
; a6 -- syslib pointer
; a5 -- device pointer
; a4 -- task (NOT process) pointer
; d7 -- wait mask

; some dos magic, useful for Processes (not us).  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.
; The next instruction after the segment list is the first executable address

    cnop    0,4     ; long word align
    DC.L    16	    ; segment length -- any number will do (this is 4
		    ; bytes back from the segment pointer)
    DC.L    0	    ; pointer to next segment

    PUTMSG  35,<'%s/Task_Begin'>
    move.l  ABSEXECBASE,a6

    ;------ Grab the argument passed down from our parent
    move.l  4(sp),a3           ; Unit pointer
    move.l  mdu_Device(a3),a5  ; Point to device structure

     ;------ 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   #INTB_PORTS,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

    ;------ Allocate a 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) ;Make message port "live"
    ;------ change the bit number into a mask, and save in d7
    moveq   #0,d7	;Clear D7
    bset    d0,d7

     move.l  $114(a6),-(sp)
     move.l  a5,-(sp)
     move.l  a3,-(sp)
     move.l  d0,-(sp)
     PUTMSG  40,<'%s/Signal=%ld, Unit=%lx Device=%lx Task=%lx'>
     add.l   #4*4,sp

    bra.s   Task_StartHere

; 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.
; (The first message will probably be posted BEFORE our task gets a chance
; to run)
;------     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

    ;------ no more messages.  back ourselves out.
    and.b   #$ff&(~(UNITF_ACTIVE!UNITF_INTASK)),UNIT_FLAGS(a3)
    ;------ main loop: wait for a new message

    PUTMSG   75,<'%s/++Sleep'>
    move.l  d7,d0
    CALLSYS Wait
	bchg.b	#1,$bfe001  ;Blink the power LED
    PUTMSG   75,<'%s/++Wakeup'>
    ;------ see if we are stopped
    btst    #MDUB_STOPPED,UNIT_FLAGS(a3)
    bne.s   Task_MainLoop	; device is stopped, ignore messages
    ;------ lock the device
    bset    #UNITB_ACTIVE,UNIT_FLAGS(a3)
    bne     Task_MainLoop	; device in use (immediate command?)

   ;------ get the next request
    move.l  a3,a0
    CALLSYS GetMsg
    PUTMSG  1,<'%s/GotMsg'>
    tst.l   d0
    beq     Task_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   Task_NextMessage

; 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 RETURN 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.	Similar code has been used
; successfully in other, "REAL" device drivers.


;   A1 should be pointing to the unit structure upon entry! (IS_DATA)
*      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 occurred

	move.l	mdu_Device(a1),a0   ; Get device pointer
	move.l	mdu_SigMask(a1),d0
	lea.l	mdu_tcb(a1),a1
	move.l	md_SysLib(a0),a6   ; Get pointer to system

;      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


;   ------ Initialize the device

    INITBYTE	MP_FLAGS,PA_IGNORE  ;Unit starts with a message port
    INITLONG	LN_NAME,myName	    ;
    INITLONG	mdu_tcb+LN_NAME,myName
    INITBYTE	mdu_tcb+LN_PRI,5
     INITBYTE	 mdu_is+LN_PRI,4      ; Int priority 4
     INITLONG	 mdu_is+IS_CODE,myintr	; Interrupt routine addr
     INITLONG	 mdu_is+LN_NAME,myName
    DC.W   0

*   ;------ Initialize packet for MakeDosNode

    INITLONG	mdn_execName,myName	; Address of driver name
    INITLONG	mdn_tableSize,12	; # long words in AmigaDOS env.
    INITLONG	mdn_dName,$524d0000	; Store 'RM' in name
    INITLONG	mdn_sizeBlock,SECTOR/4	; # 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,SECTORSPER ; secs/track (must be reasonable)
    INITLONG	mdn_resBlks,1		; reserved blocks, MUST > 0!
    INITLONG	mdn_upperCyl,(RAMSIZE/BYTESPERTRACK)-1 ; upper cylinder
    INITLONG	mdn_numBuffers,1	; # AmigaDOS buffers to start
    DC.W   0

; EndCode is a marker that shows the end of your code.	Make sure it does not
; span hunks, and is not before the rom tag!  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 (this program has only a single code hunk).
EndCode:    END

[Back to Amiga Developer Docs]