erickson@cbmvax.cbm.UUCP (Lee Erickson) (10/21/86)
The following source is intended to serve as a demo disk device driver. Since I can't email you "demo" hardware, it functions as a RAM disk. I don't really recommend that you USE it as a RAM disk, but it should be very helpful to those of you trying to implement hard disk drivers.... Lee Erickson #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # asmsupp.i # messages.i # mydev.i # mydev.asm # testdev.asm # makefile # This archive created: Mon Oct 20 17:20:51 1986 export PATH; PATH=/bin:$PATH echo shar: extracting "'asmsupp.i'" '(1056 characters)' if test -f 'asmsupp.i' then echo shar: will not over-write existing file "'asmsupp.i'" else sed 's/^ X//' << \SHAR_EOF > 'asmsupp.i' X X************************************************************************* X* * X* Copyright (C) 1985, Commodore Amiga Inc. All rights reserved. * X* Permission granted for non-commercial use * * X* * X************************************************************************* X X X************************************************************************* X* X* asmsupp.i -- random low level assembly support routines X* X* Source Control X* ------ ------- X* X* $Header: asmsupp.i,v 31.1 85/10/13 23:12:33 neil Exp $ X* X* $Locker: $ X* X************************************************************************* X XCLEAR MACRO ; quick way to clear a D register on 68000 X MOVEQ #0,\1 X ENDM X XBHS MACRO X BCC.\0 \1 X ENDM X XBLO MACRO X BCS.\0 \1 X ENDM X XEVEN MACRO ; word align code stream X DS.W 0 X ENDM X XLINKSYS MACRO ; link to a library without having to see a _LVO X LINKLIB _LVO\1,\2 X ENDM X XCALLSYS MACRO ; call a library without having to see _LVO X CALLLIB _LVO\1 X ENDM X XXLIB MACRO ; define a library reference without the _LVO X XREF _LVO\1 X ENDM X SHAR_EOF if test 1056 -ne "`wc -c < 'asmsupp.i'`" then echo shar: error transmitting "'asmsupp.i'" '(should have been 1056 characters)' fi fi # end of overwriting check echo shar: extracting "'messages.i'" '(853 characters)' if test -f 'messages.i' then echo shar: will not over-write existing file "'messages.i'" else sed 's/^ X//' << \SHAR_EOF > 'messages.i' X X************************************************************************* X* * X* Copyright (C) 1985, Commodore Amiga Inc. All rights reserved. * X* Permission granted for non-commercial use * X* * X************************************************************************* X X X************************************************************************* X* X* messages.i X* X* Source Control X* ------ ------- X* X* $Header: messages.i,v 31.1 85/10/13 23:12:48 neil Exp $ X* X* $Locker: $ X* X************************************************************************* X X XREF KPutFmt X XPUTMSG: MACRO * level,msg X X IFGE INFO_LEVEL-\1 X X PEA subSysName(PC) X MOVEM.L A0/A1/D0/D1,-(SP) X LEA msg\@,A0 X LEA 4*4(SP),A1 X JSR KPutFmt X MOVEM.L (SP)+,D0/D1/A0/A1 X ADDQ.L #4,SP X BRA.S end\@ X Xmsg\@ DC.B \2 X DC.B 10 X DC.B 0 X DS.W 0 Xend\@ X ENDC X ENDM SHAR_EOF if test 853 -ne "`wc -c < 'messages.i'`" then echo shar: error transmitting "'messages.i'" '(should have been 853 characters)' fi fi # end of overwriting check echo shar: extracting "'mydev.i'" '(4411 characters)' if test -f 'mydev.i' then echo shar: will not over-write existing file "'mydev.i'" else sed 's/^ X//' << \SHAR_EOF > 'mydev.i' X X****************************************************************** X* * X* Copyright (C) 1986, Commodore Amiga Inc. All rights reserved. * X* Permission granted for non-commercial use * * X* * X****************************************************************** X X X***************************************************************** X* X* mydev.i -- external declarations for skeleton device X* X* SOURCE CONTROL X* ------ ------- X* $Header: ramlib.i,v 31.1 85/10/13 23:12:51 neil Exp $ X* X* $Locker: neil $ X* X***************************************************************** XINFO_LEVEL EQU 0 ; Specify amount of debugging info desired X*INTRRUPT SET 1 ; Remove "*" to enable fake interrupt code X X; stack size and priority for the process we will create XMYPROCSTACKSIZE EQU $800 XMYPROCPRI EQU 0 X XSECTOR EQU 512 ; # bytes per sector XSECSHIFT EQU 9 ; Shift count to convert byte # to sector # XRAMSIZE EQU 512*200 ; Use this much RAM per unit XIAMPULLING EQU 7 ; "I am pulling the interrupt" bit of INTCRL1 XINTENABLE EQU 4 ; "Interrupt Enable" bit of INTCRL2 XINTCTRL1 EQU $40 ; Interrupt control register offset on board XINTCTRL2 EQU $42 ; Interrupt control register offset on board XINTACK EQU $50 ; My board's interrupt reset address X;----------------------------------------------------------------------- X; X; device command definitions X; X;----------------------------------------------------------------------- X X DEVINIT X DEVCMD CMD_MOTOR ; control the disk's motor (NO-OP) X DEVCMD CMD_SEEK ; explicit seek (NO-OP) X DEVCMD CMD_FORMAT ; format disk - equated to WRITE for RAMDISK X DEVCMD CMD_REMOVE ; notify when disk changes (NO-OP) X DEVCMD CMD_CHANGENUM ; number of disk changes (always 0) X DEVCMD CMD_CHANGESTATE ; is there a disk in the drive? (always TRUE) X DEVCMD CMD_PROTSTATUS ; is the disk write protected? (always FALSE) X DEVCMD CMD_RAWREAD ; Not supported X DEVCMD CMD_RAWWRITE ; Not supported X DEVCMD CMD_GETDRIVETYPE; Get drive type X DEVCMD CMD_GETNUMTRACKS; Get number of tracks X DEVCMD CMD_ADDCHANGEINT; Add disk change interrupt (NO-OP) X DEVCMD CMD_REMCHANGEINT; Remove disk change interrupt ( NO-OP) X DEVCMD MYDEV_END ; place marker -- first illegal command # X X;----------------------------------------------------------------------- X; X; Layout of parameter packet for MakeDosNode X; X;----------------------------------------------------------------------- X X STRUCTURE MkDosNodePkt,0 X APTR mdn_dosName ; Pointer to DOS file handler name X APTR mdn_execName ; Pointer to device driver name X ULONG mdn_unit ; Unit number X ULONG mdn_flags ; OpenDevice flags X ULONG mdn_tableSize ; Environment size X ULONG mdn_sizeBlock ; # longwords in a block X ULONG mdn_secOrg ; sector origin -- unused X ULONG mdn_numHeads ; number of surfaces X ULONG mdn_secsPerBlk ; secs per logical block -- unused X ULONG mdn_blkTrack ; secs per track X ULONG mdn_resBlks ; reserved blocks -- MUST be at least 1! X ULONG mdn_prefac ; unused X ULONG mdn_interleave ; interleave X ULONG mdn_lowCyl ; lower cylinder X ULONG mdn_upperCyl ; upper cylinder X ULONG mdn_numBuffers ; number of buffers X ULONG mdn_memBufType ; Type of memory for AmigaDOS buffers X STRUCT mdn_dName,5 ; DOS file handler name "RAM0" X LABEL mdn_Sizeof ; Size of this structure X X;----------------------------------------------------------------------- X; X; device data structures X; X;----------------------------------------------------------------------- X X; maximum number of units in this device XMD_NUMUNITS EQU 4 X X STRUCTURE MyDev,LIB_SIZE X ULONG md_SysLib X ULONG md_SegList X ULONG md_Base ; Base address of this device's expansion board X UBYTE md_Flags X UBYTE md_pad X STRUCT md_Units,MD_NUMUNITS*4 X LABEL MyDev_Sizeof X X STRUCTURE MyDevMsg,MN_SIZE X APTR mdm_Device X APTR mdm_Unit X LABEL MyDevMsg_Sizeof X X STRUCTURE MyDevUnit,UNIT_SIZE X UBYTE mdu_UnitNum X UBYTE mdu_SigBit ; Signal bit allocated for interrupts X APTR mdu_Device X STRUCT mdu_stack,MYPROCSTACKSIZE X STRUCT mdu_is,IS_SIZE ; Interrupt structure X STRUCT mdu_tcb,TC_SIZE ; TCB for disk task X STRUCT mdu_Msg,MyDevMsg_Sizeof X ULONG mdu_SigMask ; Signal these bits on interrupt X STRUCT mdu_RAM,RAMSIZE ; RAM used to simulate disk X LABEL MyDevUnit_Sizeof X X ;------ state bit for unit stopped X BITDEF MDU,STOPPED,2 X XMYDEVNAME MACRO X DC.B 'mydev.device',0 X ENDM X SHAR_EOF if test 4411 -ne "`wc -c < 'mydev.i'`" then echo shar: error transmitting "'mydev.i'" '(should have been 4411 characters)' fi fi # end of overwriting check echo shar: extracting "'mydev.asm'" '(30251 characters)' if test -f 'mydev.asm' then echo shar: will not over-write existing file "'mydev.asm'" else sed 's/^ X//' << \SHAR_EOF > 'mydev.asm' X X************************************************************************* X* * X* Copyright (C) 1986, Commodore Amiga Inc. All rights reserved. * X* Permission granted for non-commercial use * * X* * X************************************************************************* X X X************************************************************************* X* X* mydev.asm -- Skeleton device code. Modified by Lee Erickson to be a X* simple disk device, using RAM to simulate a disk. X* 10/7/86 X* X************************************************************************/ X SECTION section X X NOLIST X include "exec/types.i" X include "exec/nodes.i" X include "exec/lists.i" X include "exec/libraries.i" X include "exec/devices.i" X include "exec/io.i" X include "exec/alerts.i" X include "exec/initializers.i" X include "exec/memory.i" X include "exec/resident.i" X include "exec/ables.i" X include "exec/errors.i" X include "exec/tasks.i" X include 'libraries/expansion.i' X include 'libraries/configvars.i' X include 'libraries/configregs.i' X X include "asmsupp.i" X include "messages.i" X X include "mydev.i" X X LIST X X ;------ These don't have to be external, but it helps some X ;------ debuggers to have them globally visible X XDEF Init X XDEF Open X XDEF Close X XDEF Expunge X XDEF Null X XDEF myName X XDEF BeginIO X XDEF AbortIO X X XREF _AbsExecBase X X XLIB AddIntServer X XLIB RemIntServer X XLIB Debug X XLIB InitStruct X XLIB OpenLibrary X XLIB CloseLibrary X XLIB Alert X XLIB FreeMem X XLIB Remove X XLIB AllocMem X XLIB AddTask X XLIB PutMsg X XLIB RemTask X XLIB ReplyMsg X XLIB Signal X XLIB GetMsg X XLIB Wait X XLIB WaitPort X XLIB AllocSignal X XLIB SetTaskPri X XLIB GetCurrentBinding ; Get list of boards for this driver X XLIB MakeDosNode X XLIB AddDosNode X X INT_ABLES X X X ; The first executable location. This should return an error X ; in case someone tried to run you as a program (instead of X ; loading you as a library). XFirstAddress: X CLEAR d0 X rts X X;----------------------------------------------------------------------- X; A romtag structure. Both "exec" and "ramlib" look for X; this structure to discover magic constants about you X; (such as where to start running you from...). X;----------------------------------------------------------------------- X X ; Most people will not need a priority and should leave it at zero. X ; the RT_PRI field is used for configuring the roms. Use "mods" from X ; wack to look at the other romtags in the system XMYPRI EQU 0 X XinitDDescrip: X ;STRUCTURE RT,0 X DC.W RTC_MATCHWORD ; UWORD RT_MATCHWORD X DC.L initDDescrip ; APTR RT_MATCHTAG X DC.L EndCode ; APTR RT_ENDSKIP X DC.B RTF_AUTOINIT ; UBYTE RT_FLAGS X DC.B VERSION ; UBYTE RT_VERSION X DC.B NT_DEVICE ; UBYTE RT_TYPE X DC.B MYPRI ; BYTE RT_PRI X DC.L myName ; APTR RT_NAME X DC.L idString ; APTR RT_IDSTRING X DC.L Init ; APTR RT_INIT X ; LABEL RT_SIZE X X X ; this is the name that the device will have XsubSysName: XmyName: MYDEVNAME X XExLibName EXPANSIONNAME ; Expansion Library Name X X ; a major version number. XVERSION: EQU 1 X X ; A particular revision. This should uniquely identify the bits in the X ; device. I use a script that advances the revision number each time X ; I recompile. That way there is never a question of which device X ; that really is. XREVISION: EQU 17 X X ; this is an identifier tag to help in supporting the device X ; format is 'name version.revision (dd MON yyyy)',<cr>,<lf>,<null> XidString: dc.b 'mydev 1.0 (31 Oct 1985)',13,10,0 X X ; force word allignment X ds.w 0 X X X ; The romtag specified that we were "RTF_AUTOINIT". This means X ; that the RT_INIT structure member points to one of these X ; tables below. If the AUTOINIT bit was not set then RT_INIT X ; would point to a routine to run. X XInit: X DC.L MyDev_Sizeof ; data space size X DC.L funcTable ; pointer to function initializers X DC.L dataTable ; pointer to data initializers X DC.L initRoutine ; routine to run X X XfuncTable: X X ;------ standard system routines X dc.l Open X dc.l Close X dc.l Expunge X dc.l Null X X ;------ my device definitions X dc.l BeginIO X dc.l AbortIO X X ;------ function table end marker X dc.l -1 X X X ; The data table initializes static data structures. X ; The format is specified in exec/InitStruct routine's X ; manual pages. The INITBYTE/INITWORD/INITLONG routines X ; are in the file "exec/initializers.i". The first argument X ; is the offset from the device base for this byte/word/long. X ; The second argument is the value to put in that cell. X ; The table is null terminated XdataTable: X INITBYTE LH_TYPE,NT_DEVICE X INITLONG LN_NAME,myName X INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED X INITWORD LIB_VERSION,VERSION X INITWORD LIB_REVISION,REVISION X INITLONG LIB_IDSTRING,idString X DC.L 0 X X X ; This routine gets called after the device has been allocated. X ; The device pointer is in D0. The segment list is in a0. X ; If it returns non-zero then the device will be linked into X ; the device list. XinitRoutine: X X; Register Usage X; ============== X; a3 -- Points to tempory RAM X; a4 -- Expansion library base X; a5 -- device pointer X; a6 -- Exec base X; X;---------------------------------------------------------------------- X ;------ get the device pointer into a convenient A register X PUTMSG 30,<'%s/Init: called'> X movem.l d1-d7/a0-a5,-(sp) ; Preserve ALL modified registers X move.l d0,a5 X X ;------ save a pointer to exec X move.l a6,md_SysLib(a5) X X ;------ save a pointer to our loaded code X move.l a0,md_SegList(a5) X X lea.l ExLibName,A1 ; Get expansion lib. name X moveq.l #0,D0 X CALLSYS OpenLibrary ; Open the expansion library X tst.l D0 X bne.s init_OpSuccess X Xinit_OpFail: X ALERT AG_OpenLib!AO_ExpansionLib X Xinit_OpSuccess: X move.l D0,A4 X X lea md_Base(A5),A0 ; Get the Current Bindings X moveq #4,D0 ; Just get address (length = 4 bytes) X LINKLIB _LVOGetCurrentBinding,A4 X move.l md_Base(A5),D0 ; Get start of list X tst.l D0 ; If controller not found X beq Init_End ; Exit and unload driver X move.l D0,A0 ; Get config structure address X move.l cd_BoardAddr(A0),md_Base(A5); Save board base address X bclr.b #CDB_CONFIGME,cd_Flags(A0); Mark board as configured X XInit_End: X;---------------------------------------------------------------------- X; X; Here we build a packet describing the characteristics of our disk to X; pass to AmigaDOS. This serves the same purpose as a "mount" command X; of this device would. For disks, it might be useful to actually X; get this information right from the disk itself. Just as mount, X; it could be for multiple partitions on the single physical device. X; For this example, we will simply hard code the appropriate parameters. X; X;----------------------------------------------------------------------- X X;!!!! Normally you would only do this if your card was successfully configured X;!!!! up above. For the demo, it will always be done. X X ;------ Allocate tempory RAM to build MakeDosNode parameter packet X move.l #MEMF_CLEAR!MEMF_PUBLIC,d1 X move.l #mdn_Sizeof,d0 ; Enough room for our parameter packet X CALLSYS AllocMem X move.l d0,a3 X X ;----- Use InitStruct to initialize the constant portion of packet X move.l d0,a2 ; Point to memory to initialize X moveq.l #0,d0 ; Don't need to re-zero it X lea.l mdn_Init(pc),A1 X CALLSYS InitStruct X X lea mdn_dName(a3),a0 ; Get addr of Device name X move.l a0,mdn_dosName(a3) ; and save in environment X X moveq #1,d6 ; Now tell AmigaDOS about all units XUloop: X move.b d6,d0 ; Get unit number X add.b #$2F,d0 ; Make ASCII, minus 1 X move.b d0,mdn_dName+3(a3) ; and store in name X move.l d6,mdn_unit(a3) ; Store unit # in environment X X move.l a3,a0 X LINKLIB _LVOMakeDosNode,a4 ; Build AmigaDOS structures X move.l d0,a0 ; Get deviceNode address X moveq.l #0,d0 ; Set device priority to 0 X moveq.l #0,d1 X* moveq.l #ADNF_STARTPROC,d1 ; Have handler started X LINKLIB _LVOAddDosNode,a4 X X addq #1,d6 ; Bump unit number X cmp.b #MD_NUMUNITS,d6 X ble.s Uloop ; Loop until all units installed X X move.l a3,a1 ; Return RAM to system X move.l #mdn_Sizeof,d0 X CALLSYS FreeMem X X move.l a4,a1 ; Now close expansion library X CALLSYS CloseLibrary X ; X ; You would normally set d0 to a NULL if your initialization failed, X ; but I'm not doing that for this demo, since it is unlikely X ; you actually have a board with this particular manufacturer ID X ; installed when running this demo. X ; X move.l a5,d0 X movem.l (sp)+,d1-d7/a0-a5 X X rts X X;---------------------------------------------------------------------- X; X; here begins the system interface commands. When the user calls X; OpenLibrary/CloseLibrary/RemoveLibrary, this eventually gets translated X; into a call to the following routines (Open/Close/Expunge). Exec X; has already put our device pointer in a6 for us. Exec has turned X; off task switching while in these routines (via Forbid/Permit), so X; we should not take too long in them. X; X;---------------------------------------------------------------------- X X X ; Open sets the IO_ERROR field on an error. If it was successfull, X ; we should set up the IO_UNIT field. X XOpen: ; ( device:a6, iob:a1, unitnum:d0, flags:d1 ) X PUTMSG 30,<'%s/Open: called'> X movem.l d2/a2/a3/a4,-(sp) X X move.l a1,a2 ; save the iob X X ;------ see if the unit number is in range X subq #1,d0 ; Unit ZERO isn't allowed X cmp.l #MD_NUMUNITS,d0 X bcc.s Open_Error ; unit number out of range X X ;------ see if the unit is already initialized X move.l d0,d2 ; save unit number X lsl.l #2,d0 X lea.l md_Units(a6,d0.l),a4 X move.l (a4),d0 X bne.s Open_UnitOK X X ;------ try and conjure up a unit X bsr InitUnit X X ;------ see if it initialized OK X move.l (a4),d0 X beq.s Open_Error X XOpen_UnitOK: X move.l d0,a3 ; unit pointer in a3 X X move.l d0,IO_UNIT(a2) X X ;------ mark us as having another opener X addq.w #1,LIB_OPENCNT(a6) X addq.w #1,UNIT_OPENCNT(a3) X X ;------ prevent delayed expunges X bclr #LIBB_DELEXP,md_Flags(a6) X moveq.l #0,d0 X XOpen_End: X X movem.l (sp)+,d2/a2/a3/a4 X rts X XOpen_Error: X move.b #IOERR_OPENFAIL,IO_ERROR(a2) X move.b #IOERR_OPENFAIL,d0 X bra.s Open_End X X ; There are two different things that might be returned from X ; the Close routine. If the device is no longer open and X ; there is a delayed expunge then Close should return the X ; segment list (as given to Init). Otherwise close should X ; return NULL. X XClose: ; ( device:a6, iob:a1 ) X movem.l d1/a2-a3,-(sp) X PUTMSG 30,<'%s/Close: called'> X X move.l a1,a2 X X move.l IO_UNIT(a2),a3 X X ;------ make sure the iob is not used again X moveq.l #-1,d0 X move.l d0,IO_UNIT(a2) X move.l d0,IO_DEVICE(a2) X X ;------ see if the unit is still in use X subq.w #1,UNIT_OPENCNT(a3) X X;!!!!!! Since this example is a RAM disk (and we don't want the contents to X;!!!!!! disappear between opens, ExpungeUnit will be skipped here. It would X;!!!!!! be used for drivers of "real" devices X;!!!!!! bne.s Close_Device X;!!!!!! bsr ExpungeUnit X XClose_Device: X ;------ mark us as having one fewer openers X moveq.l #0,d0 X subq.w #1,LIB_OPENCNT(a6) X X ;------ see if there is anyone left with us open X bne.s Close_End X X ;------ see if we have a delayed expunge pending X btst #LIBB_DELEXP,md_Flags(a6) X beq.s Close_End X X ;------ do the expunge X bsr Expunge X XClose_End: X movem.l (sp)+,d1/a2-a3 X rts X X X ; There are two different things that might be returned from X ; the Expunge routine. If the device is no longer open X ; then Expunge should return the segment list (as given to X ; Init). Otherwise Expunge should set the delayed expunge X ; flag and return NULL. X ; X ; One other important note: because Expunge is called from X ; the memory allocator, it may NEVER Wait() or otherwise X ; take long time to complete. X XExpunge: ; ( device: a6 ) X PUTMSG 30,<'%s/Expunge: called'> X X movem.l d1/d2/a5/a6,-(sp) ; Best to save ALL modified registers X move.l a6,a5 X move.l md_SysLib(a5),a6 X X ;------ see if anyone has us open X tst.w LIB_OPENCNT(a5) X;!!!!! The following line is commented out for this RAM disk demo, since X;!!!!! we don't want the RAM to be freed after FORMAT, for example. X; beq 1$ X X ;------ it is still open. set the delayed expunge flag X bset #LIBB_DELEXP,md_Flags(a5) X CLEAR d0 X bra.s Expunge_End X X1$: X ;------ go ahead and get rid of us. Store our seglist in d2 X move.l md_SegList(a5),d2 X X ;------ unlink from device list X move.l a5,a1 X CALLSYS Remove X X ; X ; device specific closings here... X ; X X ;------ free our memory X CLEAR d0 X CLEAR d1 X move.l a5,a1 X move.w LIB_NEGSIZE(a5),d1 X X sub.w d1,a1 X add.w LIB_POSSIZE(a5),d0 X add.l d1,d0 X X CALLSYS FreeMem X X ;------ set up our return value X move.l d2,d0 X XExpunge_End: X movem.l (sp)+,d1/d2/a5/a6 X rts X X XNull: X PUTMSG 30,<'%s/Null: called'> X CLEAR d0 X rts X X XInitUnit: ; ( d2:unit number, a3:scratch, a6:devptr ) X PUTMSG 30,<'%s/InitUnit: called'> X movem.l d2-d4/a2,-(sp) X X ;------ allocate unit memory X move.l #MyDevUnit_Sizeof,d0 X move.l #MEMF_PUBLIC!MEMF_CLEAR,d1 X LINKSYS AllocMem,md_SysLib(a6) X X tst.l d0 X beq InitUnit_End X X move.l d0,a3 X move.b d2,mdu_UnitNum(a3) ; initialize unit number X move.l a6,mdu_Device(a3) ; initialize device pointer X X ;------ start up the unit process. We do a trick here -- X ;------ we set his message port to PA_IGNORE until the X ;------ new process has a change to set it up. X ;------ We cannot go to sleep here: it would be very nasty X ;------ if someone else tried to open the unit X ;------ (exec's OpenDevice has done a Forbid() for us -- X ;------ we depend on this to become single threaded). X X ;------ Initialize the stack information X lea mdu_stack(a3),a0 ; Low end of stack X move.l a0,mdu_tcb+TC_SPLOWER(a3) X lea MYPROCSTACKSIZE(a0),a0 ; High end of stack X move.l a0,mdu_tcb+TC_SPUPPER(a3) X move.l a3,-(A0) ; argument -- unit ptr X move.l a0,mdu_tcb+TC_SPREG(a3) X ;------ initialize the unit's list X lea MP_MSGLIST(a3),a0 X NEWLIST a0 X lea mdu_tcb(a3),a0 X move.l a0,MP_SIGTASK(a3) X moveq.l #0,d0 ; Don't need to re-zero it X move.l a3,a2 ; InitStruct is initializing the UNIT X lea.l mdu_Init,A1 X LINKSYS InitStruct,md_SysLib(a6) X X move.l a3,mdu_is+IS_DATA(a3) ; Pass int. server unit addr. X X; Startup the task X lea mdu_tcb(a3),a1 X lea Proc_Begin(PC),a2 X move.l a3,-(sp) ; Preserve UNIT pointer X lea -1,a3 ; generate address error X ; if task ever "returns" X CLEAR d0 X LINKSYS AddTask,md_SysLib(a6) X move.l (sp)+,a3 ; restore UNIT pointer X X ;------ mark us as ready to go X move.l d2,d0 ; unit number X lsl.l #2,d0 X move.l a3,md_Units(a6,d0.l) ; set unit table X X XInitUnit_End: X movem.l (sp)+,d2-d4/a2 X rts X X ;------ got an error. free the unit structure that we allocated. XInitUnit_FreeUnit: X bsr FreeUnit X bra.s InitUnit_End X XFreeUnit: ; ( a3:unitptr, a6:deviceptr ) X move.l a3,a1 X move.l #MyDevUnit_Sizeof,d0 X LINKSYS FreeMem,md_SysLib(a6) X rts X X XExpungeUnit: ; ( a3:unitptr, a6:deviceptr ) X PUTMSG 30,<'%s/ExpungeUnit: called'> X move.l d2,-(sp) X X; X; If you can expunge you unit, and each unit has it's own interrups, X; you must remember to remove its interrupt server X; X X IFD INTRRUPT X lea.l mdu_is(a3),a1 ; Point to interrupt structure X moveq #3,d0 ; Portia interrupt bit 3 X LINKSYS RemIntServer,md_SysLib(a6) ;Now remove the interrupt server X ENDC X X ;------ get rid of the unit's task. We know this is safe X ;------ because the unit has an open count of zero, so it X ;------ is 'guaranteed' not in use. X lea mdu_tcb(a3),a1 X LINKSYS RemTask,md_SysLib(a6) X X ;------ save the unit number X CLEAR d2 X move.b mdu_UnitNum(a3),d2 X X ;------ free the unit structure. X bsr FreeUnit X X ;------ clear out the unit vector in the device X lsl.l #2,d2 X clr.l md_Units(a6,d2.l) X X move.l (sp)+,d2 X X rts X X;---------------------------------------------------------------------- X; X; here begins the device specific functions X; X;---------------------------------------------------------------------- X X; cmdtable is used to look up the address of a routine that will X; implement the device command. Xcmdtable: X DC.L Invalid ; $00000001 X DC.L MyReset ; $00000002 X DC.L RdWrt ; $00000004 Common routine for read/write X DC.L RdWrt ; $00000008 X DC.L Update ; $00000010 X DC.L Clear ; $00000020 X DC.L MyStop ; $00000040 X DC.L Start ; $00000080 X DC.L Flush ; $00000100 X DC.L Motor ; $00000200 motor (NO-OP) X DC.L Seek ; $00000400 seek (NO-OP) X DC.L RdWrt ; $00000800 format -> WRITE for RAMDISK X DC.L MyRemove ; $00001000 remove (NO-OP) X DC.L ChangeNum ; $00002000 changenum (Returns 0) X DC.L ChangeState ; $00004000 changestate (Returns 0) X DC.L ProtStatus ; $00008000 protstatus (Returns 0) X DC.L RawRead ; Not supported (INVALID) X DC.L RawWrite ; Not supported (INVALID) X DC.L GetDriveType ; Get drive type (Returns 1) X DC.L GetNumTracks ; Get number of tracks (Returns NUMTRKS) X DC.L AddChangeInt ; Add disk change interrupt (NO-OP) X DC.L RemChangeInt ; Remove disk change interrupt ( NO-OP) Xcmdtable_end: X X; this define is used to tell which commands should not be queued X; command zero is bit zero. X; The immediate commands are Invalid, Reset, Stop, Start, Flush XIMMEDIATES EQU $000001c3 X X; These commands can NEVER be done "immediately" if using interrupts, X; since they would "wait" for the interrupt forever! X; Read, Write, Format XNEVERIMMED EQU $0000080C X; X; BeginIO starts all incoming io. The IO is either queued up for the X; unit task or processed immediately. X; X XBeginIO: ; ( iob: a1, device:a6 ) X PUTMSG 30,<'%s/BeginIO: called'> X movem.l d1/a0/a3,-(sp) X X ;------ bookkeeping X move.l IO_UNIT(a1),a3 X X ;------ see if the io command is within range X move.w IO_COMMAND(a1),d0 X cmp.w #MYDEV_END,d0 X bcc BeginIO_NoCmd X X DISABLE a0 X X ;------ process all immediate commands no matter what X move.w #IMMEDIATES,d1 X btst d0,d1 X bne.s BeginIO_Immediate X X IFD INTRRUPT ; if using interrupts, X ;------ queue all NEVERIMMED commands no matter what X move.w #NEVERIMMED,d1 X btst d0,d1 X bne.s BeginIO_QueueMsg X ENDC X X ;------ see if the unit is STOPPED. If so, queue the msg. X btst #MDUB_STOPPED,UNIT_FLAGS(a3) X bne.s BeginIO_QueueMsg X X ;------ this is not an immediate command. see if the device is X ;------ busy. X bset #UNITB_ACTIVE,UNIT_FLAGS(a3) X beq.s BeginIO_Immediate X X ;------ we need to queue the device. mark us as needing X ;------ task attention. Clear the quick flag XBeginIO_QueueMsg: X BSET #UNITB_INTASK,UNIT_FLAGS(a3) X bclr #IOB_QUICK,IO_FLAGS(a1) X X ENABLE a0 X X move.l a3,a0 X LINKSYS PutMsg,md_SysLib(a6) X bra BeginIO_End X XBeginIO_Immediate: X ENABLE a0 X X bsr PerformIO X XBeginIO_End: X movem.l (sp)+,d1/a0/a3 X rts X XBeginIO_NoCmd: X move.b #IOERR_NOCMD,IO_ERROR(a1) X bra.s BeginIO_End X X X; X; PerformIO actually dispatches an io request. It expects a3 to already X; have the unit pointer in it. a6 has the device pointer (as always). X; a1 has the io request. Bounds checking has already been done on X; the io request. X; X XPerformIO: ; ( iob:a1, unitptr:a3, devptr:a6 ) X PUTMSG 30,<'%s/PerforIO: called'> X move.l a2,-(sp) X move.l a1,a2 X X clr.b IO_ERROR(A2) ; No error so far X move.w IO_COMMAND(a2),d0 X lsl #2,d0 ; Multiply by 4 to get table offset X lea cmdtable(pc),a0 X move.l 0(a0,d0.w),a0 X X jsr (a0) X X move.l (sp)+,a2 X rts X X; X; TermIO sends the IO request back to the user. It knows not to mark X; the device as inactive if this was an immediate request or if the X; request was started from the server task. X; X XTermIO: ; ( iob:a1, unitptr:a3, devptr:a6 ) X PUTMSG 30,<'%s/TermIO: called'> X move.w IO_COMMAND(a1),d0 X move.w #IMMEDIATES,d1 X btst d0,d1 X bne.s TermIO_Immediate X X ;------ we may need to turn the active bit off. X btst #UNITB_INTASK,UNIT_FLAGS(a3) X bne.s TermIO_Immediate X X ;------ the task does not have more work to do X bclr #UNITB_ACTIVE,UNIT_FLAGS(a3) X XTermIO_Immediate: X ;------ if the quick bit is still set then we don't need to reply X ;------ msg -- just return to the user. X btst #IOB_QUICK,IO_FLAGS(a1) X bne.s TermIO_End X X LINKSYS ReplyMsg,md_SysLib(a6) X XTermIO_End: X rts X X XAbortIO: ; ( iob: a1, device:a6 ) X;---------------------------------------------------------------------- X; X; here begins the functions that implement the device commands X; all functions are called with: X; a1 -- a pointer to the io request block X; a2 -- another pointer to the iob X; a3 -- a pointer to the unit X; a6 -- a pointer to the device X; X; Commands that conflict with 68000 instructions have a "My" prepended X; to them. X;---------------------------------------------------------------------- X XRawRead: ; 10 Not supported (INVALID) XRawWrite: ; 11 Not supported (INVALID) XInvalid: X move.b #IOERR_NOCMD,IO_ERROR(a1) X bsr TermIO X rts X XMyReset: XAddChangeInt: XRemChangeInt: XMyRemove: XSeek: XMotor: XChangeNum: XChangeState: XProtStatus: X clr.l IO_ACTUAL(a1) ; Indicate drive isn't protected X bsr TermIO X rts X XGetDriveType: X move.l #1,IO_ACTUAL(a1) ; Make it look like 3.5" X bsr TermIO X rts X XGetNumTracks: X move.l #RAMSIZE/5120,IO_ACTUAL(a1) ; Number of 10 sector tracks X bsr TermIO X rts X XRdWrt: X movem.l a2/a3,-(sp) X clr.l IO_ACTUAL(a1) ; Initially, no data moved X move.l IO_DATA(a1),a0 X move.l IO_LENGTH(a1),d0 X X ;------ deal with zero length I/O X beq.s RdWrt_end X X X move.l IO_UNIT(a1),a3 ; Get unit pointer X move.l a1,a2 X X* check operation for legality X X move.l IO_OFFSET(a2),d0 X move.l d0,d1 X X* check for being an even sector boundary X and.l #SECTOR-1,d1 X bne IO_Err X X* check for IO within disc range X add.l IO_LENGTH(a2),d0 X cmp.l #RAMSIZE,d0 X bgt IO_Err X X* We've gotten this far, it must be a valid request. X X IFD INTRRUPT X move.l mdu_SigMask(a3),d0 ; Get signals to wait for X LINKSYS Wait,md_SysLib(a6) ; Wait for interrrupt before proceeding X ENDC X X move.l IO_OFFSET(a2),d0 X lea mdu_RAM(a3),a0 ; Point to RAMDISK "sector" for I/O X add.l d0,a0 ; Can't use index, "out of range" X move.l IO_LENGTH(a2),d0 X move.l d0,IO_ACTUAL(a2) ; Indicate we've moved all bytes X subq.w #1,d0 ; Adjust byte count for DBRA loop X move.l IO_DATA(a2),a1 ; Point to data buffer X X move.w IO_COMMAND(a2),d1 ; Now go to correct loop for X cmp.b #CMD_READ,d1 ; Read or Write commands X BEQ.S RdLp X XWrtLp: move.b (a1)+,(a0)+ ; Copy a byte X dbra d0,WrtLp ; Move all requested bytes X bra.s RdWrt_end X XRdLp: move.b (a0)+,(a1)+ ; Copy a byte X dbra d0,RdLp ; Move all requested bytes X bra.s RdWrt_end X XIO_Err: X move.b #IOERR_BADLENGTH,IO_ERROR(a1) X XRdWrt_end: X move.l a2,a1 X bsr TermIO X movem.l (sp)+,a2/a3 X rts X X; X; Update and Clear are internal buffering commands. Update forces all X; io out to its final resting spot, and does not return until this is X; done. Clear invalidates all internal buffers. Since this device X; has no internal buffers, these commands do not apply. X; X XUpdate: X PUTMSG 30,<'%s/Update: called'> X bra Invalid XClear: X PUTMSG 30,<'%s/Clear: called'> X bra Invalid X X; X; the Stop command stop all future io requests from being X; processed until a Start command is received. The Stop X; command is NOT stackable: e.g. no matter how many stops X; have been issued, it only takes one Start to restart X; processing. X; X XMyStop: X PUTMSG 30,<'%s/MyStop: called'> X bset #MDUB_STOPPED,UNIT_FLAGS(a3) X X bsr TermIO X rts X XStart: X PUTMSG 30,<'%s/Start: called'> X bsr InternalStart X X move.l a2,a1 X bsr TermIO X X rts X XInternalStart: X ;------ turn processing back on X bclr #MDUB_STOPPED,UNIT_FLAGS(a3) X X ;------ kick the task to start it moving X move.l a3,a1 X CLEAR d0 X move.l MP_SIGBIT(a3),d1 X bset d1,d0 X LINKSYS Signal,md_SysLib(a3) X X rts X X; X; Flush pulls all io requests off the queue and sends them back. X; We must be careful not to destroy work in progress, and also X; that we do not let some io requests slip by. X; X; Some funny magic goes on with the STOPPED bit in here. Stop is X; defined as not being reentrant. We therefore save the old state X; of the bit and then restore it later. This keeps us from X; needing to DISABLE in flush. It also fails miserably if someone X; does a start in the middle of a flush. X; X XFlush: X PUTMSG 30,<'%s/Flush: called'> X movem.l d2/a6,-(sp) X X move.l md_SysLib(a6),a6 X X bset #MDUB_STOPPED,UNIT_FLAGS(a3) X sne d2 X XFlush_Loop: X move.l a3,a0 X CALLSYS GetMsg X X tst.l d0 X beq.s Flush_End X X move.l d0,a1 X move.b #IOERR_ABORTED,IO_ERROR(a1) X CALLSYS ReplyMsg X X bra.s Flush_Loop X XFlush_End: X X move.l d2,d0 X movem.l (sp)+,d2/a6 X X tst.b d0 X beq.s 1$ X X bsr InternalStart X1$: X X move.l a2,a1 X bsr TermIO X X rts X X; X; Foo and Bar are two device specific commands that are provided just X; to show you how to add your own commands. The currently return that X; no work was done. X; X XFoo: XBar: X CLEAR d0 X move.l d0,IO_ACTUAL(a1) X X bsr TermIO X rts X X;---------------------------------------------------------------------- X; X; here begins the process related routines X; X; A Process is provided so that queued requests may be processed at X; a later time. X; X; X; Register Usage X; ============== X; a3 -- unit pointer X; a6 -- syslib pointer X; a5 -- device pointer X; a4 -- task (NOT process) pointer X; d7 -- wait mask X; X;---------------------------------------------------------------------- X X; some dos magic. A process is started at the first executable address X; after a segment list. We hand craft a segment list here. See the X; the DOS technical reference if you really need to know more about this. X X cnop 0,4 ; long word allign X DC.L 16 ; segment length -- any number will do Xmyproc_seglist: X DC.L 0 ; pointer to next segment X X; the next instruction after the segment list is the first executable address X XProc_Begin: X X move.l _AbsExecBase,a6 X X ;------ Grab the argument X move.l 4(sp),a3 ; Unit pointer X X move.l mdu_Device(a3),a5 ; Point to device structure X X IFD INTRRUPT X ;------ Allocate a signal for "I/O Complete" interrupts X moveq #-1,d0 ; -1 is any signal at all X CALLSYS AllocSignal X move.b d0,mdu_SigBit(A3) ; Save in unit structure X X moveq #0,d7 ; Convert bit number signal mask X bset d0,d7 X move.l d7,mdu_SigMask(A3) ; Save in unit structure X X lea.l mdu_is(a3),a1 ; Point to interrupt structure X moveq #3,d0 ; Portia interrupt bit 3 X CALLSYS AddIntServer ; Now install the server X X move.l md_Base(a5),a0 ; Get board base address X* bset.b #INTENABLE,INTCTRL2(a0) ; Enable interrupts X ENDC X X ;------ Allocate the right signal X X moveq #-1,d0 ; -1 is any signal at all X CALLSYS AllocSignal X X move.b d0,MP_SIGBIT(a3) X move.b #PA_SIGNAL,MP_FLAGS(a3) X X ;------ change the bit number into a mask, and save in d7 X X moveq #0,d7 X bset d0,d7 X X ;------ X ;------ OK, kids, we are done with initialization. We now X ;------ can start the main loop of the driver. It goes X ;------ like this. Because we had the port marked PA_IGNORE X ;------ for a while (in InitUnit) we jump to the getmsg X ;------ code on entry. X ;------ X ;------ wait for a message X ;------ lock the device X ;------ get a message. if no message unlock device and loop X ;------ dispatch the message X ;------ loop back to get a message X ;------ X X bra.s Proc_CheckStatus X X ;------ main loop: wait for a new message XProc_MainLoop: X move.l d7,d0 X CALLSYS Wait X XProc_CheckStatus: X ;------ see if we are stopped X btst #MDUB_STOPPED,UNIT_FLAGS(a3) X bne.s Proc_MainLoop ; device is stopped X X ;------ lock the device X bset #UNITB_ACTIVE,UNIT_FLAGS(a3) X bne.s Proc_MainLoop ; device in use X X ;------ get the next request XProc_NextMessage: X move.l a3,a0 X CALLSYS GetMsg X tst.l d0 X beq.s Proc_Unlock ; no message? X X ;------ do this request X move.l d0,a1 X exg a5,a6 ; put device ptr in right place X bsr PerformIO X exg a5,a6 ; get syslib back in a6 X X bra.s Proc_NextMessage X X ;------ no more messages. back ourselves out. XProc_Unlock: X and.b #$ff&(~(UNITF_ACTIVE!UNITF_INTASK)),UNIT_FLAGS(a3) X bra Proc_MainLoop X X; X; Here is a dummy interrupt handler, with some crucial components commented X; out. If the IFD INTRRUPT is enabled, this code will cause the device to X; wait for a level two interrupt before it will process each request X; (pressing a key on the keyboard will do it). This code is normally X; disabled, and must fake or omit certain operations since there isn't X; really any hardware for this driver. Similiar code has been used X; successfully in other, "REAL" device drivers. X; X X IFD INTRRUPT X; A1 should be pointing to the unit structure upon entry! X Xmyintr: move.l mdu_Device(a1),a0 ; Get device pointer X move.l md_SysLib(a0),a6 ; Get pointer to system X move.l md_Base(a0),a0 ; point to board base address X* btst.b #IAMPULLING,INTCTRL1(a0);See if I'm interrupting X* beq.s myexnm ; if not set, exit, not mine X* move.b #0,INTACK(a0) ; toggle controller's int2 bit X X; ------ signal the task that an interrupt has occured X X move.l mdu_SigMask(a1),d0 X lea mdu_tcb(a1),a1 X CALLSYS Signal X X; X; now clear the zero condition code so that X; the interrupt handler doesn't call the next X; interrupt server. X; X* moveq #1,d0 clear zero flag X* bra.s myexit now exit X; X; this exit point sets the zero condition code X; so the interrupt handler will try the next server X; in the interrupt chain X; Xmyexnm moveq #0,d0 set zero condition code X; Xmyexit rts X ENDC X Xmdu_Init: X; ------ Initialize the device X X INITBYTE MP_FLAGS,PA_IGNORE X INITBYTE LN_TYPE,NT_DEVICE X INITLONG LN_NAME,myName X INITBYTE mdu_Msg+LN_TYPE,NT_MSGPORT;Unit starts with MsgPort X INITLONG mdu_Msg+LN_NAME,myName X INITLONG mdu_tcb+LN_NAME,myName X INITBYTE mdu_tcb+LN_TYPE,NT_TASK X INITBYTE mdu_tcb+LN_PRI,5 X INITBYTE mdu_is+LN_PRI,4 ; Int priority 4 X IFD INTRRUPT X INITLONG mdu_is+IS_CODE,myintr ; Interrupt routine addr X ENDC X INITLONG mdu_is+LN_NAME,myName X DC.L 0 X Xmdn_Init: X* ;------ Initialize packet for MakeDosNode X X INITLONG mdn_execName,myName ; Address of driver name X INITLONG mdn_tableSize,11 ; # long words in AmigaDOS env. X INITLONG mdn_dName,$52414d00 ; Store 'RAM' in name X INITLONG mdn_sizeBlock,128 ; # longwords in a block X INITLONG mdn_numHeads,1 ; RAM disk has only one "head" X INITLONG mdn_secsPerBlk,1 ; secs/logical block, must = "1" X INITLONG mdn_blkTrack,10 ; secs/track (must be reasonable) X INITLONG mdn_resBlks,1 ; reserved blocks, MUST > 0! X INITLONG mdn_upperCyl,(RAMSIZE/5120)-1; upper cylinder X INITLONG mdn_numBuffers,1 ; # AmigaDOS buffers to start X DC.L 0 X X;---------------------------------------------------------------------- X; EndCode is a marker that show the end of your code. X; Make sure it does not span sections nor is before the X; rom tag in memory! It is ok to put it right after X; the rom tag -- that way you are always safe. I put X; it here because it happens to be the "right" thing X; to do, and I know that it is safe in this case. X;---------------------------------------------------------------------- XEndCode: X X END SHAR_EOF if test 30251 -ne "`wc -c < 'mydev.asm'`" then echo shar: error transmitting "'mydev.asm'" '(should have been 30251 characters)' fi fi # end of overwriting check echo shar: extracting "'testdev.asm'" '(2021 characters)' if test -f 'testdev.asm' then echo shar: will not over-write existing file "'testdev.asm'" else sed 's/^ X//' << \SHAR_EOF > 'testdev.asm' X X************************************************************************* X* * X* Copyright (C) 1985, Commodore Amiga Inc. All rights reserved. * X* Permission granted for non-commercial use * * X* * X************************************************************************/ X X X************************************************************************* X* X* testdev.asm -- test the mylib.asm code X* X* Source Control X* ------ ------- X* X* $Header: amain.asm,v 31.3 85/10/18 19:04:04 neil Exp $ X* X* $Locker: neil $ X* X* $Log: amain.asm,v $ X* X************************************************************************/ X X INCLUDE 'exec/types.i' X INCLUDE 'exec/libraries.i' X INCLUDE 'exec/devices.i' X INCLUDE 'exec/io.i' X INCLUDE 'exec/tasks.i' X INCLUDE 'exec/interrupts.i' X X INCLUDE 'asmsupp.i' X INCLUDE 'mydev.i' X X X X XDEF _main X X XREF _printf X XREF _AbsExecBase X XREF _CreatePort X XREF _DeletePort X XREF _CreateStdIO X XREF _DeleteStdIO X X XLIB OpenDevice X XLIB CloseDevice X X X_main: X move.l _AbsExecBase,a6 X X ;------ make a reply port X pea 0 X pea myName X jsr _CreatePort X addq.l #8,sp X X move.l d0,Port X beq.s main_end X X ;------ get an io request X move.l d0,-(sp) X jsr _CreateStdIO X addq.l #4,sp X X move.l d0,Iob X beq main_DeletePort X X move.l d0,a1 X move.l #myName,LN_NAME(a1) X X ;------ open the test device: this will bring it in from disk X lea myDevName(pc),a0 X moveq.l #1,d0 X moveq.l #0,d1 X CALLSYS OpenDevice X X tst.l d0 X beq.s 1$ X X ;------ couldn't find the library X pea 0 X move.l d0,a0 X move.b IO_ERROR(a0),3(sp) X pea myDevName(pc) X pea nodevmsg(pc) X jsr _printf X addq.l #8,sp X X bra main_DeleteIob X X1$: X X ;------ close the device X move.l Iob,a1 X CALLSYS CloseDevice X Xmain_DeleteIob: X move.l Iob,-(sp) X jsr _DeleteStdIO X addq.l #4,sp X Xmain_DeletePort X move.l Port,-(sp) X jsr _DeletePort X addq.l #4,sp X Xmain_end: X rts X XmyDevName: MYDEVNAME XmyName: dc.b 'testdev',0 Xnodevmsg: dc.b 'can not open device "%s": error %ld',10,0 Xtestmsg: dc.b 'function MYFUNC%ld returned %ld',10,0 X XPort: dc.l 0 XIob: dc.l 0 X X END SHAR_EOF if test 2021 -ne "`wc -c < 'testdev.asm'`" then echo shar: error transmitting "'testdev.asm'" '(should have been 2021 characters)' fi fi # end of overwriting check echo shar: extracting "'makefile'" '(968 characters)' if test -f 'makefile' then echo shar: will not over-write existing file "'makefile'" else sed 's/^ X//' << \SHAR_EOF > 'makefile' X X#****************************************************************** X#* * X#* Copyright (C) 1986, Commodore Amiga Inc. All rights reserved. * X#* Permission granted for non-commercial use * * X#* * X#****************************************************************** X XOBJ = mydev.o testdev.o X X.asm.o: X echo "Assembling $*.asm" X Assem $*.asm -o $@ -i :include -c S -c W150000 -v $*.err X Xtdev: mydev.device testdev X echo "DONE!" X Xtestdev: testdev.o X echo "Linking $*" X alink FROM startup.obj+testdev.o to testdev LIBRARY SYS:lib/amiga.lib ver tlink.err X X$(OBJ) : mydev.i asmsupp.i X Xmydev.device: mydev.o X echo "Linking $@" X alink FROM mydev.o to mydev.device LIBRARY SYS:lib/amiga.lib+\ Xsys:lib/debug.lib ver mlink.err X echo "Copying $@ to devs:" X copy $@ devs:$@ X echo "Copying $* to df0:expansion:" X copy mydev.device df0:expansion/$* SHAR_EOF if test 968 -ne "`wc -c < 'makefile'`" then echo shar: error transmitting "'makefile'" '(should have been 968 characters)' fi fi # end of overwriting check # End of shell archive exit 0 -- Lee Erickson - now working with, uucp: {ihnp4|seismo|caip}!cbmvax!erickson but no way officially representing arpa: cbmvax!erickson@seismo.css.GOV Commodore, Engineering Department