;*********************************************************************
;*                                                                   *
;* All changes I made are hereby in the Public Domain.  Commodore    *
;* retains its copyright, if any, of the original code in the Rom    *
;* Kernel manuals since this is a derivative work.  -Jeff Rush       *
;*                                                                   *
;*********************************************************************
;*                           History                                 *
;*                                                                   *
;* 20Sep89 jrr Original crib from Amiga Rom Kernel Manual.           *
;* 23Jul90 jrr Extensive descriptive prose added.                    *
;*                                                                   *
;*********************************************************************
        TITLE   Example Device Driver for Tutorial Use
        SMALLOBJ    ; Use PC-Relative Addressing
        OPTIMON     ; Enable C.A.P.E. 68K Optimizations

        SECTION TheOnlySection

        ;************
        ;* Includes *
        ;************
        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 "exec/semaphores.i"
        INCLUDE "hardware/intbits.i"

        INCLUDE "asmsupp.i"

        INCLUDE "Example.i"     ; Device-Specific Definitions
        LIST

        ;*************
        ;* Constants *
        ;*************

        ;******************
        ;* Public Symbols *
        ;******************
        XDEF    InitUnit        ; Initialize a Unit Descriptor and Fire Up a Task
        XDEF    FreeUnit        ; Deallocate the Memory for the Unit Descriptor
        XDEF    ExpungeUnit     ; Terminate a Unit Task and Deallocate the Unit Descriptor

        ;********************
        ;* External Symbols *
        ;********************
        ; This Name is for Debugging Use, to Annotate Serial-Debug Output
        IFNE    INFO_LEVEL      ; If Any Debugging Enabled at All
        XREF    subSysName
        ENDC

        ; Defined in Ex_Support.Asm
        XREF    PerformIO       ; Beginning of I/O Dispatch Point

        ; Defined in Ex_Main.Asm
        XREF    MyName          ; Name of This Device (for Tagging Ports and Tasks)

;*********************************************************************
;*                     InitUnit Helper Routine                       *
;*                                                                   *
;* InitUnit() is used to allocate a unit descriptor and optionally   *
;* spawn a task to manage that unit.                                 *
;*                                                                   *
;* Inputs:                                                           *
;*   A6 - Ptr to our Device node.                                    *
;*   A3 - Scratch Register.                                          *
;*   D2 - Unit Number Desired.                                       *
;*                                                                   *
;* Outputs:                                                          *
;*   D0        - ?????                                               *
;*                                                                   *
;* All Original Registers Must Be Preserved Across Function Call!!!! *
;*                                                                   *
;*********************************************************************
InitUnit:
        PUTMSG  30,<'%s/InitUnit: called'>
        MOVEM.L D2-D4/A2,-(SP)              ; Preserve Original Registgers
        ;
        ; Allocate Some Memory for a Unit Descriptor Structure
        MOVE.L  #MyDevUnit_Sizeof,D0        ; Size of Structure --> D0
        MOVE.L  #MEMF_PUBLIC!MEMF_CLEAR,D1  ; Type of Memory    --> D1
        LINKSYS AllocMem,md_SysLib(A6)      ; Allocate Memory
        TST.L   D0                          ; Did It Work?
        BEQ     InitUnit_End                ; Nope, Exit Function Early
        MOVE.L  D0,A3                       ; Save Ptr to Allocation in A3
        ;
        ; Initialize the Unit Descriptor
        MOVEQ.L #0,D0               ; Signal InitStruct() to Not Re-Zero Structure
        MOVE.L  A3,A2               ; Ptr to Structure to Initialize    --> A2
        LEA.L   mdu_Init(PC),A1     ; Ptr to Initialization Definitions --> A1
        LINKSYS InitStruct,md_SysLib(A6)    ; Initialize Unit Descriptor

        MOVE.B  D2,mdu_UnitNum(A3)      ; Initialize the Unit Number
        MOVE.L  A6,mdu_Device(A3)       ; Initialize the Ptr to the Device Node

        ;
        ; Start up the unit task.  We do a trick here; we set his message
        ; port to PA_IGNORE until the new task has a chance 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 Boundaries
        LEA     mdu_Stack(A3),A0            ; Define Low End of Task Stack
        MOVE.L  A0,mdu_Tcb+TC_SPLOWER(A3)
        LEA     TASKSTKSIZE(A0),A0          ; Define High End of Task Stack
        MOVE.L  A0,mdu_Tcb+TC_SPUPPER(A3)

        MOVE.L  A3,-(A0)                    ; Pass Ptr to Unit Descriptor on New Task's Stack
        MOVE.L  A0,mdu_Tcb+TC_SPREG(A3)     ; Define Position of New Task's Stack Pointer
        LEA     mdu_Tcb(A3),A0              ; Ptr to Unit's Task Structure --> A0
        MOVE.L  A0,MP_SIGTASK(A3)           ; Set Message Port's Monitor Task to Unit's Task

        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

        LEA     MP_MSGLIST(A3),A0
        NEWLIST A0                      ; Initialize the Message Port's List
        ;
        ; Start the Unit Task
        LEA     mdu_Tcb(A3),A1          ; Pickup a Ptr to the Task Control Block
        LEA     UnitTask_Begin(PC),A2   ; Pickup Ptr to Task's Code Entry Point
        MOVE.L  A3,-(SP)                ; Preserve the Ptr to the Unit Descriptor
        ;
        ; If Unit Task ever Returns, Generate an Artificial Address Error
        LEA     -1,A3                   ; It Should Never Return, We RemTask() It when Done
        CLEAR   D0
        PUTMSG  30,<'%s/About to add task'>
        LINKSYS AddTask,md_SysLib(A6)
        MOVE.L  (SP)+,A3                ; Restore the Ptr to the Unit Descriptor
        ;
        ; Mark New Unit as Ready to Operate
        MOVE.L  D2,D0                   ; Pick up Unit Number in D0
        LSL.L   #2,D0                   ; Multiply by 4 Bytes Per Entry
        MOVE.L  A3,md_Units(A6,D0.L)    ; Set Ptr to Unit Descriptor into Unit Table
        PUTMSG  30,<'%s/InitUnit: ok'>

InitUnit_End:
        MOVEM.L (SP)+,D2-D4/A2          ; Restore Original Registers
        RTS

        ;****************************************************
        ;* Initialization Definitions for a Unit Descriptor *
        ;****************************************************
mdu_Init:
        INITBYTE    MP_FLAGS,PA_IGNORE      ;\
        INITBYTE    LN_TYPE,NT_MSGPORT      ; > Message Port
        INITLONG    LN_NAME,MyName          ;/
        INITLONG    mdu_Tcb+LN_NAME,MyName  ;\
        INITBYTE    mdu_Tcb+LN_TYPE,NT_TASK ; > Task Control Block
        INITBYTE    mdu_Tcb+LN_PRI,5        ;/
        DC.L        0

;*********************************************************************
;*                     FreeUnit Helper Routine                       *
;*                                                                   *
;* FreeUnit() is used to deallocate the memory occupied by a unit    *
;* descriptor.  It assumes the task has already been stopped.        *
;*                                                                   *
;* Inputs:                                                           *
;*   A6 - Ptr to our Device node.                                    *
;*   A3 - Ptr to Unit Descriptor.                                    *
;*                                                                   *
;* Outputs:                                                          *
;*   None                                                            *
;*                                                                   *
;*********************************************************************
FreeUnit:
        MOVE.L  A3,A1
        MOVE.L  #MyDevUnit_Sizeof,D0
        LINKSYS FreeMem,md_SysLib(A6)   ; Release the Memory
        RTS

;*********************************************************************
;*                   ExpungeUnit Helper Routine                      *
;*                                                                   *
;* ExpungeUnit() is used to stop the unit task, free its descriptor  *
;* and unlink it from the unit table.                                *
;*                                                                   *
;* Inputs:                                                           *
;*   A6 - Ptr to our Device node.                                    *
;*   A3 - Ptr to Unit Descriptor.                                    *
;*                                                                   *
;* Outputs:                                                          *
;*   None                                                            *
;*                                                                   *
;*********************************************************************
ExpungeUnit:
        PUTMSG  10,<'%s/ExpungeUnit: called'>
        MOVE.L  D2,-(SP)                ; Preserve Original D2 Across Function

        LEA     mdu_Tcb(A3),A1
        LINKSYS RemTask,md_SysLib(A6)   ; Remove the Unit's Task

        CLEAR   D2
        MOVE.B  mdu_UnitNum(A3),D2      ; Save the Unit# in D2 for a Moment

        BSR     FreeUnit                ; Free the Unit Descriptor Structure Itself

        ;------ clear out the unit vector in the device
        LSL.L   #2,D2                   ; Compute an Index into the Unit Table
        CLR.L   md_Units(A6,D2.L)       ; Clear the Ptr to the Unit Descriptor

        MOVE.L  (SP)+,D2                ; Restore Original D2
        RTS

;*********************************************************************
;*                 Unit Task Code (Reentrant)                        *
;*                                                                   *
;* This block of code serves as the executable port of a spun-off    *
;* that handles each unit.  There may be multiple unit tasks using   *
;* the same code in a reentrant fashion, therefore the device author *
;* must take steps to coordinate shared accesses.                    *
;*                                                                   *
;* An alternative architecture is to create only a single task and   *
;* let all unit request flow into it.  The choice depends upon the   *
;* specific hardware being managed and your design requirements.     *
;*                                                                   *
;* Register Usage:                                                   *
;*   A3 - Ptr to the Unit Descriptor.                                *
;*   A6 - Ptr to the Exec Library.                                   *
;*   A5 - Ptr to our Device node.                                    *
;*   A4 - Ptr to Task Control Block (-NOT- a DOS Process)            *
;*   D7 - Event Wait Mask                                            *
;*                                                                   *
;* Notes:                                                            *
;*   This task never terminates, it is simply removed suddenly.      *
;*                                                                   *
;*********************************************************************

        ; 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     ; Align the Following on a Long Word Boundary
        DC.L    16      ; Length of Segment (Dummy Value, Any will Do)
UnitTask_Seglist:
        DC.L    0       ; NULL Pointer to the Next Segment

UnitTask_Begin:
        PUTMSG  35,<'%s/Task_Begin'>
        MOVE.L  ABSEXECBASE,A6      ; Pickup Ptr to Exec Library
        ;
        ; Pickup a Ptr to the Unit Descriptor as Passed to Us on
        ;   our Initial Stack
        MOVE.L  4(SP),A3            ; Pickup Ptr to Unit Descriptor
        MOVE.L  mdu_Device(A3),A5   ; Pickup Ptr to our Device node
        ;
        ; Allocate a Signal Bit for Use with Our Message Port
        MOVEQ   #-1,D0              ; Any Signal Will Do (-1 = Any)
        CALLSYS AllocSignal         ; Allocate a Signal Bit
        MOVE.B  D0,MP_SIGBIT(A3)    ; Note Signal Bit Allocated for Future
        MOVE.B  #PA_SIGNAL,MP_FLAGS(A3) ; Make our Message Port 'Live'

        MOVEQ   #0,D7               ; Change the Signal Bit# into a Mask
        BSET    D0,D7               ;   and Save in D7 for Future Reference

                                    ; Because our Message Port was Marked PA_IGNORE
                                    ;   for a while (in InitUnit), We jump to the
                                    ;   GetMsg() code upon entry.  This is because
                                    ;   our first message will probably be posted
                                    ;   -before- our task gets a chance to run.
        BRA.S   Task_StartHere      ; Jump into Incoming-Message Waiting Loop

        ;***********************
        ;* Main Loop of Device *
        ;***********************
Task_Unlock:
        AND.B   #$FF&(~(UNITF_ACTIVE!UNITF_INTASK)),UNIT_FLAGS(A3)

Task_MainLoop:
        PUTMSG  75,<'%s/++Sleep'>
        MOVE.L  D7,D0
        CALLSYS Wait            ; Wait for an Incoming Message

        IFGE    INFO_LEVEL-5
        BCHG.B  #1,$BFE001      ; Blink the Power LED for Each Message Received
        ENDC

Task_StartHere:
        PUTMSG   75,<'%s/++Wakeup'>

        BTST    #MDUB_STOPPED,UNIT_FLAGS(A3) ; Is this Unit Suspended (Stopped)?
        BNE.S   Task_MainLoop               ; Yes, Ignore Messages

        BSET    #UNITB_ACTIVE,UNIT_FLAGS(A3) ; Mark the Device as Active
        BNE     Task_MainLoop               ; If Device was Already In-Use, Ignore Message
Task_NextMessage:
        MOVE.L  A3,A0                       ; Pickup a Ptr to the Incoming Message Port
        CALLSYS GetMsg                      ; Pull a Message from the Message Port
        PUTMSG  1,<'%s/GotMsg'>
        TST.L   D0                          ; Did We Get One?
        BEQ     Task_Unlock                 ; No, Go Back to Waiting for One

        MOVE.L  D0,A1                       ; Ptr to I/O Request Block (Msg) --> A1
        EXG     A5,A6                       ; Ptr to Device node             --> A6
                                            ; Ptr to Unit Descriptor         --> A3
        BSR     PerformIO                   ; Dispatch to I/O Handler Function
        EXG     A5,A6                       ; Get Ptr to Exec Library Back in A6
        BRA.S   Task_NextMessage            ; Go Process Next Message Already at Port

;*********************************************************************
;*                                                                   *
;*********************************************************************
        END

