        DOSSEG
        .MODEL  SMALL,C


COMMENT |

  MIDIINT.ASM -- Assembler subroutines for MIDIEX patch exchange utility


  COPYRIGHT (C) 1986 John Bailin, Cantus Corporation

  Date:        01/02/86

  Changed 08/24/86 Jim Bergsten to function using the Microsoft "C"
  compiler. Summary of changes:
      1. PAGE statement added for better output listing.
      2. Entry point names prefixed with "_".
      3. GROUP, PROG, SEGMENT statements changed to match Microsoft
         small model standards.
      4. Unnecessary stack subtracts and adds removed.
      5. Other corrections, simplifications...

  Changed 11/14/86 by Michael Geary - fixed several bugs

  Modified 1/22/88 by Dave Hayes - Added support for GMR bulk dumps by
                                   increasing size of receive buffer

  Note: Linkage conventions assume the Microsoft C compiler small model.


  Modified 12/5/89 by Mike W. Smith:
        1. Complete rewrite of code.
        2. Fixed interrupt handler to work properly on AT's and 386's.
        3. Allows interrupt level and MPU base port to be changed.
        4. Allows buffer size to be changed.
        5. Changed code to MASM 5.1 simplified directives
        6. Other minor corrections

|


;------------------------------------------------------------------------------
; Equates
;------------------------------------------------------------------------------

reset                   EQU 0FFh
ready_to_receive?       EQU 01000000b   ;Mpu data set ready, active low.
data_available?         EQU 10000000b   ;Mpu data read ready, active low.

loop_delay              EQU 0FFFFh      ;Delay for timeouts.




;------------------------------------------------------------------------------

        .DATA

        extrn   buffer_size:word

        public  mpu_data_port,mpu_command_port,mpu_status_port,mpu_irq
        public  irq_mask,eoi,data_in_handler
        public  buffer_ptr,buffer_end,sysex_started,sysex_ended

mpu_data_port   dw 0330h                ;Mpu data port address.
mpu_command_port LABEL  WORD            ;Mpu command port address.
mpu_status_port dw 0331h                ;Mpu status port address.
mpu_irq         db 0Ah                  ;Mpu interrupt level
irq_mask        db 00000100b            ;8259 mask for IRQ 2
eoi             dw 0000000001100010b    ;End Of Interrupt mask

data_in_handler dw no_op                ;A NULL routine

buffer_ptr      dw 0
buffer_end      dw 0

sysex_started   db 0                    ;Sysex receive flags
sysex_ended     db 0                    ; "

old_brk_add     dw 0,0




        .CODE

old_int_vect    dw 0,0                  ;Data must be placed here for proper
                                        ; access in the ISR

;______________________________________________________________________________
;
;       MPU interrupt handler
;______________________________________________________________________________

mpu_int proc    far

        push ds                         ;Save used registers
        push dx
        push ax

        mov ax,DGROUP                   
        mov ds,ax                       ;Set DS to data area

        mov dx,mpu_status_port          ;Test to see if interrupt was initiated
        in al,dx                        ; by the MPU.
        test al,data_available?
            jz valid_mpu_data           ;Yes, goto MPU routines

        pop ax                          ;No, restore registers
        pop dx
        pop ds

        jmp dword ptr cs:old_int_vect   ;Exit out to old interrupt handler


;Get the data byte from the MPU.
valid_mpu_data:
        sti
        push cx
        push bx

        mov dx,mpu_data_port
        in al,dx

        call word ptr data_in_handler   ;Changeable data handler


;Acknowledge interrupt
        mov ax,eoi
        out 020h,al
        or ah,ah                        ;Is an AT being used?
            jz mi_exit
        mov al,ah                       ;Yes,
        out 0A0h,al                     ; acknowledge AT's second 8259

mi_exit:
        pop bx
        pop cx
        pop ax
        pop dx
        pop ds

        iret

mpu_int endp




;______________________________________________________________________________
;
;       Control Break ISR
;______________________________________________________________________________

break_routine   proc    far

        clc                             ;Signal that dos is to continue
        ret

break_routine   endp




;______________________________________________________________________________
;
;       A No Operation routine
;______________________________________________________________________________

no_op   proc

        ret

no_op   endp



;______________________________________________________________________________
;
;       Queue received mpu data.
;       input - al = received data.
;______________________________________________________________________________

receive_sysex   proc    uses bx

        cmp al,0F0h                     ;Is byte SYSEX F0h beginning?
            jne rs_1                    ;No, go on
        cli
        mov sysex_started,1             ;Yes, set flag
        jmp short rs_3
rs_1:
        cmp sysex_started,1             ;Is a SYSEX currently being inputed?
            jne rs_exit                 ;No, exit
        test al,10000000b               ;Is byte end of SYSEX?
            jz rs_3                     ;No, record SYSEX byte

;EOX, set flags and exit
rs_2:
        cli
        mov sysex_ended,1
        mov sysex_started,0
        mov al,0F7h                     ;Force EOX byte

;Store data bytes into buffer
rs_3:
        cli
        mov bx,buffer_ptr               ;Get buffer
        add bx,buffer_end             
        mov [bx],al                     ;Store SYSEX byte
        inc buffer_end                  ;Increment buffer end
        mov ax,buffer_end
        cmp ax,buffer_size              ;At the end of the buffer?
            jb rs_exit                  ;No, exit

;Buffer overflowed, set last byte to F7h EOX, set flags, and exit
        mov sysex_ended,1
        mov sysex_started,0
        mov byte ptr buffer_end[bx],0F7h ;Force EOX byte

rs_exit:
        sti
        ret

receive_sysex   endp




;______________________________________________________________________________
;
;       void reset_ctrl_brk(void);
;       Resets the Ctrl-Break ISR
;______________________________________________________________________________

reset_ctrl_brk  proc    uses ds

        mov dx,old_brk_add
        mov ds,old_brk_add+2
        mov ax,02523h
        int 021h

        ret

reset_ctrl_brk  endp



;______________________________________________________________________________
;
;       void reset_mpu_vector(void);
;       Resets the interrupt vector for the MPU-401
;______________________________________________________________________________

reset_mpu_vector        proc    uses ds

;Mask off the IRQ line first
        cmp mpu_irq,06Fh                ;Using an AT?
            ja rmv_1                    ;Yes, reset second 8259
        cli
        in al,21h                       ;Get current mask
        jmp short $+2                   ;Delay for fast processors
        or al,irq_mask                  ;Set IRQ mask bit
        out 21h,al                      ;Mask IRQ off
        jmp short rmv_2
rmv_1:
        cli
        in al,0A1h                      ;Read 8259 mask
        jmp short $+2                   ;Delay for fast processors
        or al,irq_mask                  ;Set IRQ mask bit
        out 0A1h,al                     ;Mask IRQ off
rmv_2:
        sti

;Then restore the old interrupt vector
        mov ah,25h
        mov al,mpu_irq
        mov dx,old_int_vect             ;Put old offset into DX
        mov ds,old_int_vect+2           ;Put old segment into DS
        int 21h                         ;Reset the vector
        ret                             ;Exit

reset_mpu_vector        endp



;______________________________________________________________________________
;
;       int second_8259(void);
;       Detects a second 8259
;       Returns SUCCESS = 1, FAILURE = 0.
;______________________________________________________________________________

second_8259     proc    uses es

        mov ax,0C000h                   ;Get system configuration
        int 015h                        ;Extended service for AT's
        mov ax,0
            jc s8_exit                  ;Carry is set, service not supported
        cmp byte ptr es:[bx+3],0FBh     ;XT model?
            je s8_exit                  ;Yes, exit
        cmp byte ptr es:[bx+3],0FEh     ;XT model?
            je s8_exit                  ;Yes, exit
        test byte ptr es:[bx+6],040h    ;Is bit for 2nd 8259 set?
            jz s8_exit                  ;No, no second 8259
        mov ax,1                        ;Yes, set AX
s8_exit:
        ret

second_8259     endp



;______________________________________________________________________________
;
;       int send_command(int command);
;       Sends a command to the MPU-401.
;
;       returns SUCCESS=-1, FAILURE=0, or command response byte
;______________________________________________________________________________

send_command    proc    command:byte

        pushf                           ;Save flags (interrupt flag)

;Wait for MPU to be ready to receive
        mov cx,loop_delay               ;Timeout loop limit
        mov dx,mpu_status_port
sc_1:
        in al,dx
        test al,ready_to_receive?
            jz sc_2                     ;Ready, send byte
        loop sc_1

;MPU not ready, exit with error
        xor ax,ax
        jmp short sc_10

;Send command
sc_2:
        cli                             ;Suspend interrupts
        mov al,command                  ;Get command byte
        out dx,al                       ;Send it

;Wait for the command acknowledge
sc_3:
        mov cx,loop_delay               ;Timeout loop limit
        mov dx,mpu_status_port
sc_4:
        in al,dx
        test al,data_available?
            jz sc_5                     ;Data available, go get it
        loop sc_4                       ;Keep trying

;Command ack not returned, exit with error.
        xor ax,ax                       
        jmp short sc_10

;Get data in
sc_5:
        mov dx,mpu_data_port
        in al,dx
        cmp al,0FEh                     ;Is it an acknowledge?
            je sc_6                     ;Yes, continue

        call word ptr data_in_handler   ;No, have the main data handler handle
                                        ; the data byte
        jmp short sc_3                  ;Try again

;Test to see if command sent requires a returned data byte
sc_6:
        mov ax,-1                       ;Successful return code
        cmp command,0A0h                ;Less than A0h?
            jb sc_10                    ;Yes, no data byte returned
        cmp command,0AFh                ;More than AFh?
            ja sc_10                    ;Yes, no data byte returned

;Get returning data byte
        mov cx,loop_delay               ;Timeout loop limit
        mov dx,mpu_status_port
sc_7:
        in al,dx
        test al,data_available?
            jz sc_8                     ;Data available, go get it
        loop sc_7                       ;Try again

;Command failed to return a data byte, exit with error
        xor ax,ax
        jmp short sc_10

;Get data byte
sc_8:
        mov dx,mpu_data_port
        in al,dx
        xor ah,ah

;Exit with return code in AX
sc_10:
        sti                             ;Restore interrupts
        popf                            ;Restore flags
        ret

send_command    endp




;______________________________________________________________________________
;
;       int send_data(int databyte);
;       Writes data to the MPU.
;
;       Returns SUCCESS=1, FAILURE=0.
;______________________________________________________________________________

send_data       proc    databyte:byte

        pushf                           ;Save flags (interrupt flag)

;Wait for MPU to be ready to receive
        mov cx,loop_delay               ;Timeout loop limit
        mov dx,mpu_status_port
sd_1:
        in al,dx
        test al,ready_to_receive?
            jz send_d                   ;MPU is ready, send data
        loop sd_1                       ;MPU not ready yet, try again

;MPU not ready, exit with error
        xor ax,ax
        jmp short sd_exit

;Send the data byte
send_d:
        cli
        mov al,databyte                 ;Get data byte
        mov dx,mpu_data_port
        out dx,al                       ;Send it
        mov ax,1                        ;Set AX for no error

sd_exit:
        sti
        popf                            ;Restore flags
        ret

send_data       endp




;______________________________________________________________________________
;
;       void set_ctrl_brk(void);
;       Sets the Ctrl-Break ISR
;______________________________________________________________________________

set_ctrl_brk    proc    uses es ds

        mov ax,03523h                   ;Get original break address
        int 021h
        mov old_brk_add,bx
        mov old_brk_add+2,es
        mov dx,offset break_routine
        mov ax,seg break_routine        ;Set for new break address
        mov ds,ax
        mov ax,02523h
        int 021h

        ret

set_ctrl_brk    endp




;______________________________________________________________________________
;
;       void set_handler(void (*address)(void));
;       Sets the data in handler
;______________________________________________________________________________

set_handler     proc    address:word

           cli
           mov ax,address
           mov data_in_handler,ax
           sti

           ret

set_handler     endp



;______________________________________________________________________________
;
;       void set_mpu_vector(void);
;       Sets the interrupt vector for the MPU-401
;______________________________________________________________________________

set_mpu_vector  proc

;Get current interrupt vector first and save
        mov ah,035h                     
        mov al,mpu_irq                  ;IRQ level to get
        int 21h
        mov cs:old_int_vect,bx          ;Save vector offset.
        mov cs:old_int_vect+2,es        ;Save vector segment.

;Set the IRQ vector to the new Interrupt Service Routine
        push ds                         ;Save DS
        mov ah,025h
        mov al,mpu_irq
        mov dx,offset mpu_int           ;ISR offset
        mov bx,seg mpu_int
        mov ds,bx                       ;ISR segment
        int 21h                         ;Set new vector
        pop ds                          ;Restore DS

;Unmask the proper IRQ line in the Interrupt controller
        cmp mpu_irq,06Fh                ;Using an AT?
            ja smv_1                    ;Yes, unmask second 8259
        cli
        in al,21h                       ;Read 8259 mask
        jmp short $+2                   ;Delay for fast processors
        mov ah,irq_mask                 ;Get new mask
        not ah                          ;Invert for ANDing
        and al,ah                       ;Reset IRQ bit
        out 21h,al                      ;Enable IRQ
        jmp short smv_2
smv_1:
        cli
        in al,0A1h                      ;Read second 8259 mask
        jmp short $+2                   ;Delay for fast processors
        mov ah,irq_mask                 ;Get new mask
        not ah                          ;Invert for ANDing
        and al,ah                       ;Reset IRQ bit
        out 0A1h,al                     ;Enable IRQ
smv_2:
        sti
        ret                             ;Exit

set_mpu_vector  endp



        END
