************************************************************************* * * 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 NOLIST 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" IFNE AUTOMOUNT include "libraries/expansion.i" include "libraries/configvars.i" include "libraries/configregs.i" ENDC LIST 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). FirstAddress: moveq #-1,d0 rts ;----------------------------------------------------------------------- ; 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 MYPRI EQU 0 initDDescrip: ;STRUCTURE RT,0 DC.W RTC_MATCHWORD ; UWORD RT_MATCHWORD (Magic cookie) 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 VERSION ; UBYTE RT_VERSION DC.B NT_DEVICE ; UBYTE RT_TYPE (must be correct) DC.B MYPRI ; BYTE RT_PRI 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 subSysName: dc.b "ramdev",0 ENDC ; this is the name that the device will have myName: MYDEVNAME IFNE AUTOMOUNT ExLibName dc.b 'expansion.library',0 ; Expansion Library Name ENDC ; 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. REVISION: EQU 1 ; 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. 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 ;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 ; dataTable: INITBYTE LN_TYPE,NT_DEVICE ;Must be LN_TYPE! INITLONG LN_NAME,myName INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED INITWORD LIB_VERSION,VERSION INITWORD LIB_REVISION,REVISION INITLONG LIB_IDSTRING,idString DC.W 0 ;terminate list ;-------- initRoutine ------------------------------------------------------- ; ; FOR RTF_AUTOINIT: ; 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. ; ; IMPORTANT: ; 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 ;---------------------------------------------------------------------- initRoutine: ;------ 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) IFNE AUTOMOUNT ************************************************************************** * * 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 Uloop: 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 Init_End: 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. ************************************************************************* ENDC move.l a5,d0 Init_Error: movem.l (sp)+,d1-d7/a0-a5 rts ;---------------------------------------------------------------------- ; ; 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. ; ; IMPORTANT: ; 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 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) ;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" Open_End: subq.w #1,LIB_OPENCNT(a6) ;** End of expunge protection <|> movem.l (sp)+,d2/a2/a3/a4 rts Open_Range_Error: Open_Error: 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 Close_Device: 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 Close_End: 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 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 ;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 Expunge_End: movem.l (sp)+,d1/d2/a5/a6 rts ;------- Null --------------------------------------------------------------- Null: PUTMSG 1,<'%s/Null: called'> CLEAR d0 rts ;The "Null" function MUST return NULL. ;------- Custom ------------------------------------------------------------ ; ;Two "do nothing" device-specific functions ; FunctionA: add.l d1,d0 ;Add rts FunctionB: add.l d0,d0 ;Double 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 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) IFGE INFO_LEVEL-30 move.l a0,-(SP) move.l a3,-(SP) PUTMSG 30,<'%s/InitUnit, unit= %lx, task=%lx'> addq.l #8,sp ENDC ;------ 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) IFD INTRRUPT move.l a3,mdu_is+IS_DATA(a3) ; Pass unit addr to interrupt server ENDC ; 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'> InitUnit_End: movem.l (sp)+,d2-d4/a2 rts ;--------------------------------------------------------------------------- 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 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 ; IFD INTRRUPT 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 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 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. ; cmdtable: 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) cmdtable_end: ; 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 ENDC ;-------------------------------- ; 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. ; ; IMPORTANT: ; 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 ) IFGE INFO_LEVEL-1 bchg.b #1,$bfe001 ;Blink the power LED ENDC IFGE INFO_LEVEL-3 clr.l -(sp) move.w IO_COMMAND(a1),2(sp) ;Get entire word PUTMSG 3,<'%s/BeginIO -- $%lx'> addq.l #4,sp ENDC 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 ENDC ;------ 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 BeginIO_QueueMsg: bset #UNITB_INTASK,UNIT_FLAGS(a3) bclr #IOB_QUICK,IO_FLAGS(a1) ;We did NOT complete this quickly ENABLE a0 IFGE INFO_LEVEL-250 move.l a1,-(sp) move.l a3,-(sp) PUTMSG 250,<'%s/PutMsg: Port=%lx Message=%lx'> addq.l #8,sp ENDC 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 ;------ BeginIO_Immediate: ENABLE a0 bsr.s PerformIO BeginIO_End: PUTMSG 200,<'%s/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 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 ) IFGE INFO_LEVEL-150 clr.l -(sp) move.w IO_COMMAND(a1),2(sp) ;Get entire word PUTMSG 150,<'%s/PerformIO -- $%lx'> addq.l #4,sp ENDC 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) 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) ;a1-message ;(ReplyMsg sets the LN_TYPE to NT_REPLYMSG) TermIO_End: rts ***************************************************************************** ; ; 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" rts RawRead: ; 10 Not supported (INVALID) RawWrite: ; 11 Not supported (INVALID) 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. ; Update: Clear: 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 GetNumTracks: 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. ; Foo: Bar: 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. ;--------------------------------------------------------------------------- RdWrt: IFGE INFO_LEVEL-200 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 ENDC 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... ;[D0=offset] 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... ;[D0=offset] * check for IO within disc range ;[D0=offset] 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. IFD INTRRUPT move.l mdu_SigMask(a3),d0 ; Get signals to wait for LINKSYS Wait,md_SysLib(a6) ; Wait for interrupt before proceeding ENDC 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 ;D0=length ; cmp.b #CMD_READ,IO_COMMAND+1(a2) ; Decide on direction BEQ.S CopyTheBlock EXG A0,A1 ; For Write and Format, swap source & dest CopyTheBlock: LINKSYS CopyMemQuick,md_SysLib(a6) ;A0=source A1=dest D0=size ;CopyMemQuick is very fast RdWrt_end: move.l a2,a1 movem.l (sp)+,a2/a3 bra TermIO ;END IO_LenErr: PUTMSG 10,<'bad length'> move.b #IOERR_BADLENGTH,IO_ERROR(a2) IO_End: 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 MyStop: PUTMSG 30,<'%s/MyStop: called'> bset #MDUB_STOPPED,UNIT_FLAGS(a3) bra TermIO Start: PUTMSG 30,<'%s/Start: called'> bsr.s InternalStart bra TermIO ;[A3=unit A6=device] InternalStart: move.l a1,-(sp) ;------ turn processing back on bclr #MDUB_STOPPED,UNIT_FLAGS(a3) ;------ 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 rts ; ; 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...) ; Flush: PUTMSG 30,<'%s/Flush: called'> movem.l d2/a1/a6,-(sp) move.l md_SysLib(a6),a6 bset #MDUB_STOPPED,UNIT_FLAGS(a3) sne d2 Flush_Loop: 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 Flush_End: move.l d2,d0 movem.l (sp)+,d2/a1/a6 tst.b d0 beq.s 1$ bsr InternalStart 1$: 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) myproc_seglist: DC.L 0 ; pointer to next segment Task_Begin: 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 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 #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 ENDC ;------ 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 IFGE INFO_LEVEL-40 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 ENDC 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. Task_Unlock: and.b #$ff&(~(UNITF_ACTIVE!UNITF_INTASK)),UNIT_FLAGS(a3) ;------ main loop: wait for a new message Task_MainLoop: PUTMSG 75,<'%s/++Sleep'> move.l d7,d0 CALLSYS Wait IFGE INFO_LEVEL-5 bchg.b #1,$bfe001 ;Blink the power LED ENDC Task_StartHere: 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 Task_NextMessage: 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. ; IFD INTRRUPT ; A1 should be pointing to the unit structure upon entry! (IS_DATA) myintr: * 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 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 ;Unit starts with a message port INITBYTE LN_TYPE,NT_MSGPORT ; INITLONG LN_NAME,myName ; INITLONG mdu_tcb+LN_NAME,myName INITBYTE mdu_tcb+LN_TYPE,NT_TASK INITBYTE mdu_tcb+LN_PRI,5 IFD INTRRUPT INITBYTE mdu_is+LN_PRI,4 ; Int priority 4 INITLONG mdu_is+IS_CODE,myintr ; Interrupt routine addr INITLONG mdu_is+LN_NAME,myName ENDC DC.W 0 IFNE AUTOMOUNT mdn_Init: * ;------ 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 ENDC ;---------------------------------------------------------------------- ; 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