************************************************************************* * * * 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)',,, 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