@ab	equ 6		;use big model
;
; the following constants are hardware specific to the MPU401 & OP4000
; MIDI interface controllers
;
BASE_ADDRESS_MPU 	equ 330h
STATUS_PORT_MPU 	equ BASE_ADDRESS_MPU  + 1
COMMAND_PORT_MPU 	equ BASE_ADDRESS_MPU  + 1
DATA_PORT_MPU 		equ BASE_ADDRESS_MPU  + 0
Tx_EMT 			equ 40h	;midi transmitter ready for more data
Rx_NEMT			equ 80h	;midi receiver has data ready
MPU_INT			equ 2
MPU_INT_MASK		equ 4
;
; the remaining constants are for the AT&T PC6300 and compatible PC's
;
TIMER_INT		equ 0
TIMER_INT_MASK		equ 1
HW_INT_MASK		equ MPU_INT_MASK OR TIMER_INT_MASK
INT_CTLR_PORT		equ 20h	;port to send EOI command to
INT_CTLR_IMR 		equ 21h	;port to mask interrupts
EOI_CODE		equ 20h	;EOI command
T_55MS	   equ	0000h		; 8253 value for 55 ms
T_5MS	   equ	174dh		; 8253 value for 5 ms
TIC_55MS   equ	11		; Number of timer tics to equal 55 ms
PIT_MODE   equ	36h	; 8253 mode: 00 11 011 0 -> cntr0,LSB/MSB,mode 3,binary
PIT_CNT0   equ	40h		; Counter 0 port for 8253 PIT
PIT_CTRL   equ	43h		; Control port for 8253 PIT
USER_TMR   equ	1ch		; User Timer (55 ms) Int. Vector
;

;********************
;**   macros       **
;********************
;
c_in	macro			;this macro sets up a stack frame
    push	bp		;must save bp reg for 'c' progs
    mov	bp, sp			;set up frame pointer
    push di
    push si			;save register vars
    endm
;
c_out	macro			;This macro cleans up for return to 'C'
    pop si
    pop di
    mov sp, bp
    pop	bp			;restore caller's frame pointer
    cld				;clear direction flag 
    ret				;and return
    endm
;
push_all_regs	macro
    push	es
    push	ds
    push	dx
    push	cx
    push	bx
    push	si
    push	di
    push	ax
    push	bp
    endm
;
pop_all_regs	macro
    pop	bp
    pop	ax
    pop	di
    pop	si
    pop	bx
    pop	cx
    pop	dx
    pop	ds
    pop	es
    endm
;
enable_IMR	macro num		;this macro clears (enables)
					;interrupts in the interrupt mask reg
    in		al, INT_CTLR_IMR	;read in current int mask
    and		al, 0ffh - num		;clear the requested IMR bit
    out		INT_CTLR_IMR, al	;and write it back out
    endm
;
disable_IMR	macro num		;disable interrupts in the IMR
    in		al, INT_CTLR_IMR	;read in current int mask
    or		al, num			;set the requested IMR bit
    out		INT_CTLR_IMR, al	;and write it out
    endm
;
ack_int	macro				;this macro sends non-specific EOI
    					;to the int controller chip in the PC
    mov al, EOI_CODE
    out INT_CTLR_PORT, al
    sti
    endm
;

;************************************************************
;**                                                        **
;** DATA GROUP DECLARATION                                 **
;**                                                        **
;************************************************************
;
	
dgroup	group	data
data	segment	word public 'data'
	assume	ds:dgroup
;
; --- The following vars are used to manage a very small stack
;     that is used for interrupt enable/disable stacking
;
INT_STACK_SIZE		equ	8 * 1
int_stack		db	(INT_STACK_SIZE/8) dup ('intstack')
int_stack_pointer 	dw	0
;
; --- The following provide a circular character buffer used to
;     store data received from the MIDI interface
;
BUFFER_SIZE		equ	512
_wrt_index		dw	0
_rd_index		dw	0
_midi_buffer		db	BUFFER_SIZE dup (0)
public _wrt_index
public _rd_index
public _midi_buffer
;
; --- The following vars are used to keep a running tally of the
;     programmable timer ticks.
;
_hzcount		dd	0
int_cnt			dw 	0
public	_hzcount
;
data	ends
;

;************************************************************
;**                                                        **
;** CODE SEGMENT                                           **
;**                                                        **
;**                                                        **
;************************************************************
;
;
    extrn _fatal_dos_handler:far	;'C' routine to handle DOS errors
    extrn _bye:far			; exit routine for Ctl-brk

_code segment byte public 'code'
    assume cs:_code
    public     _ctl_brk
    public     _asm_fatal_dos_handler
    public     _disable_ints
    public     _enable_ints
    public     _init_enable_MPU
    public     _init_enable_TIMER
    public     _reset_TIMER
    public     _write_data_MPU
    public     _read_MPU
    public     _midi_isr
    public     _tick_isr

;
;************************************************************
;**                                                        **
;** INTERRUPT HANDLERS                                     **
;**                                                        **
;**                                                        **
;************************************************************
_ctl_brk	proc	far
;
;	Control-Break Interrupt Service Routine
;
ctl_st: cli			; make sure no interrupts
	mov	ax,ds
	mov	ss,ax		; ss = ds :: so chkstk() doesn't fail in quit()
	mov	sp,0fffeh	;  and sp = hi mem
	mov	ax,1		; exit code
	sti			; turn interrupts back on
	push	ax
	call    _bye
	pop	bp		; not needed, never returns here
	iret			;  or here
_ctl_brk	endp
;
;	Fatal Dos Error Interrupt Service Routine
;
_asm_fatal_dos_handler proc far
	push_all_regs		;save machine state
	push	di
	push	ax		;push args for function
	mov	ax, dgroup
	mov	ds, ax		;address data segment
	mov	es, ax		;and extra segmant
	call	_fatal_dos_handler ;handler(ax, di)
	pop	ax
	pop	ax
	pop_all_regs
	mov	al,0		;tell DOS to ignore error
	iret
_asm_fatal_dos_handler endp
;
;
;************************************************************
;** MIDI_ISR                                               **
;** This is the MPU int service routine             	   **
;** It reads data from MPU data port until there is no more** 
;** and stores in a circular buffer			   **
;************************************************************
;
_midi_isr	proc	far
	push_all_regs				;save machine state
	mov	ax, dgroup
	mov	ds, ax
	disable_IMR MPU_INT_MASK
	sti
do_another:
	mov	dx, DATA_PORT_MPU
	in	al, dx		;read in the interrupt data from MPU

        inc	_wrt_index			;bump up write pointer
        cmp	_wrt_index, 01ffh		;is index at top of buff?
        jle	wrt_index_ok
        mov	_wrt_index, 0			;if yes, wraparound to bottom
wrt_index_ok:					;of buffer 
        mov	bx, _wrt_index
        mov	_midi_buffer[bx], al		;move data into buffer

        mov dx, STATUS_PORT_MPU			;DX = &status port
        in	al, dx				;read status port
        and al, Rx_NEMT				;is there data to read?
	jz	do_another			;go back if so
;isr return
	cli					;turn off system ints
	enable_IMR MPU_INT_MASK
	ack_int					;and acknowlege interrupt
	pop_all_regs				;restore machine state
	iret					;and leave
_midi_isr	endp
;
;********************************************************************
;*	TICK_ISR
;*	The interrupt service routine for the programmable timer.
;*	Timer is currently set at 5 ms. intervals.
;********************************************************************
;
_tick_isr 	proc far
;
tmr_st:	push	ds
	push	ax
	mov	ax, dgroup
	mov	ds, ax		;must have our data segment
	disable_IMR TIMER_INT_MASK
	sti
	add	word ptr _hzcount, 1
	adc	word ptr _hzcount + 2, 0
	inc	int_cnt
	cmp	int_cnt,TIC_55MS
	jge	tm1
	cli
	enable_IMR TIMER_INT_MASK
	ack_int
	pop	ax
	pop	ds
	iret

tm1:	mov	int_cnt,0
	int	USER_TMR
	cli
	enable_IMR TIMER_INT_MASK
	ack_int
	pop	ax
	pop	ds
	iret
_tick_isr endp


;
;************************************************************
;** DISABLE_INTS()                                         **
;** Called to disable interrupts.  stacks old int          **
;** status before disabling, so that nesting is            **
;** preserved.                                             **
;************************************************************
;
_disable_ints proc far
	cli
    	in	al, INT_CTLR_IMR	;read in current int mask
	mov	dl, al			;move it to dl
	and	dl, HW_INT_MASK		;extract the bit for MPU ints
	mov	bx, int_stack_pointer	;get current stack pointer
	mov	int_stack[bx], dl	;store out int status there
	inc	bx
	mov	int_stack_pointer, bx	;write back new stack pointer value
	or	al, HW_INT_MASK		;mask off int
    	out	INT_CTLR_IMR, al	;and write it out
	sti
	ret
_disable_ints endp
;
;************************************************************
;** ENABLE_INTS()                                          **
;** Restores whatever the int status was before            **
;** DISABLE_INTS() was called.                             **
;** Note that calls to enable and disable must be matched  **
;** or chaos will occur                                    **
;************************************************************
;
_enable_ints proc far
	cli
    	in	al, INT_CTLR_IMR	;read in current int mask
	and	al, 0ffh - HW_INT_MASK 	; get all the bits except the ones
					; for out ints
	mov	bx, int_stack_pointer	;get int stack pointer
	dec	bx			;move it down
	mov	int_stack_pointer, bx	;store it
	or	al, int_stack[bx]	;or in int status with old one
    	out	INT_CTLR_IMR, al	;and write it out
	sti				;turn ints on now
	ret
_enable_ints endp
;
;************************************************************
;** INIT_ENABLE_MPU()                                  **
;** Starts up the MPU ints.                               **
;**                                                        **
;************************************************************
;
_init_enable_MPU proc far
	cli
	enable_IMR MPU_INT_MASK
	sti
	ret
_init_enable_MPU endp
;;
;************************************************************
;** INIT_ENABLE_TIMER()                                  **
;** Starts up the TIMER ints.                               **
;**                                                        **
;************************************************************
;
_init_enable_TIMER proc far
	cli
; Set 8253 PIT for different timing
;
	mov	al,PIT_MODE
	out	PIT_CTRL,al	; Set 8253 mode
	mov	ax,T_5MS
	out	PIT_CNT0,al
	mov	al,ah
	out	PIT_CNT0,al	; Set new timing
	enable_IMR TIMER_INT_MASK
	sti
	ret
_init_enable_TIMER endp 
;
;
;************************************************************
;** RESET_TIMER()                                          **
;** Sets timer values back to what they were               **
;**                                                        **
;************************************************************
_reset_TIMER	proc far

	disable_IMR TIMER_INT_MASK
	mov	al,PIT_MODE
	out	PIT_CTRL,al	; Set 8253 mode
	mov	ax,T_55MS
	out	PIT_CNT0,al
	mov	al,ah
	out	PIT_CNT0,al	; Reset timing
	enable_IMR TIMER_INT_MASK
	ret
_reset_TIMER	endp
;

;************************************************************
;** WRITE_DATA_MPU(DATA)                                   **
;** sends a byte to the MPU data port                      **
;** waits for handshake                                    **
;**                                                        **
;************************************************************
;
;
_write_data_MPU proc far
	c_in
	mov bx, @ab[bp]			;get data byte in bl reg
	call write_sub			;and call fast sub
	c_out				;leave
_write_data_MPU endp
;
;************************************************************
;**                                                        **
;**   write_sub                                            **
;**   Writes data to MPU. on entry, byte in bl            **
;**   ax, cx, cx destroyed                                 **
;************************************************************
;
write_sub proc near
	mov dx, STATUS_PORT_MPU	;set up pointer to MPU port
write_clear_loop:
	in	al, dx
	and al, Tx_EMT
	jnz write_clear_loop
	dec	dx			;point to data reg
	mov	al, bl
	out	dx, al			;send out the data
	ret
write_sub endp
;
;************************************************************
;** read_MPU()                                            **
;** reads a byte from the MPU	eata port                   **
;** waits for handshake                                    **
;**                                                        **
;************************************************************
;
_read_MPU proc far
	c_in
read_clear_loop:
	mov dx, STATUS_PORT_MPU	;DX = &status port
	in	al, dx
	and al, Rx_NEMT
	jnz read_clear_loop
	dec	dx			;point to data reg
	in	al, dx
	xor	ah, ah
	c_out
_read_MPU endp

;

_code	ends
	end

