	.386p
;************************************************************************/
;*	Copyright (C) 1986-1990 Phar Lap Software, Inc.			*/
;*	Unpublished - rights reserved under the Copyright Laws of the	*/
;*	United States.  Use, duplication, or disclosure by the 		*/
;*	Government is subject to restrictions as set forth in 		*/
;*	subparagraph (c)(1)(ii) of the Rights in Technical Data and 	*/
;*	Computer Software clause at 252.227-7013.			*/
;*	Phar Lap Software, Inc., 60 Aberdeen Ave., Cambridge, MA 02138	*/
;************************************************************************/

;
; This program installs bimodal (separate real-mode and protected-mode)
; handlers for the IRQ4 (COM1 serial port) hardware interrupt.  The 
; interrupt handlers just stuff received characters into a circular
; buffer.  The main program loop retrieves characters out of the buffer
; and prints them on the screen until it sees a carriage return, when
; it exits.
;
; This program does NOT set up the serial port parameters;  use the DOS
; MODE command to program the port appropriately before running this
; program.  For example, to use 9600 baud, no parity, 8 data bits, one
; stop bit, use the command:
;	mode com1 9600,n,8,1
;
; Rather than use the -REALBREAK switch (which is not DPMI-compatible)
; to get the real mode code and data into conventional memory, we
; allocate a conventional memory block at startup time and copy the
; real mode code and data into it.
;

;
; Constants and data structures
;
include	dosx.ah

IOBUF_SIZE	equ	32			; Size of circular buffer
CR		equ	0Dh			; ASCII carriage return 
LF		equ	0Ah			; ASCII linefeed
PIC_MSK		equ	21h			; PIC Mask Register port #
PIC_EOI		equ	20h			; PIC EOI Register port #
PORTNO		equ	3F8h			; COM1 base address (port #)
COM_IER		equ	PORTNO+1		; Serial port INT Enable Reg
COM_MCR		equ	PORTNO+4		; Serial port Modem Ctrl Reg
IRQ4_MSK	equ	10h			; PIC enable IRQ4 INTs mask

;
; Struct for data kept in conventional memory that also needs to be accessed
; from protected mode
;
REALDATA struc
	RD_IDX		dd	?	; current read index in IO_BUF
	WR_IDX		dd	?	; current write index in IO_BUF
	IO_BUF	db IOBUF_SIZE dup (?)	; 32 byte circular I/O buffer
REALDATA ends

;
; Segment definitions and ordering
;
_rcodeseg 	segment byte public use16 'rm_code'
	public	start_real
start_real	label	byte
_rcodeseg	ends
_rdata		segment	dword public use16 'rm_data'
_rdata		ends
_data		segment	dword public use32 'data'
_data		ends
dgroup	group	_rdata, _data
_codeseg	segment	byte public use32 'code'
_codeseg	ends
_stack		segment dword stack use32 'stack'
	db	2048 dup (?)	; 2K stack
_stack		ends

;
; Global data that needs to be accessed in both real mode and protected mode
; This data gets copied to conventional memory;  it is not used in its
; original link-time location.
;
_rdata	segment	

io_data	REALDATA	<>	; I/O buffer and circular buffer pointers

	public	end_real
end_real	label	byte

_rdata	ends

;
; Global protected mode data
;
_data	segment	

;
; Original RM and PM IRQ4 vectors
;
	public	rm_irq4_vec,pm_irq4_off,pm_irq4_sel,irq4_int
rm_irq4_vec	dd	?	; original real mode vector
pm_irq4_off	dd	?	; original prot mode vector
pm_irq4_sel	dw	?		;
irq4_int	db	?	; interrupt number for IRQ4
	align	4

;
; Conventional memory block control.  Note that the real mode CS and DS
; values for our real mode IRQ4 handler are not necessarily the same as
; the address of the memory block.  This is because if necessary we back
; them off so the link-time offsets to code and data will still be correct
; at run time.  It is necessary to do this if the real mode code and data
; don't begin at the start of the protected mode segment (eg, if the
; -OFFSET 1000h link-time switch is used).
;
cbuf_seg	dw	?	; real mode paragraph addr of memory block
rm_csds		dw	?	; real mode CS and DS values
	align	4
iodat_offs	dd	?	; offset in segment 0034h (the DOS memory
					; seg) of the io_data structure

prompt_msg db	'Send CR-terminated data over serial port 1, '
	db	'or type CTRL-C to exit',0Dh,0Ah,'$'
	align	4

_data	ends

;****************************************************************************
; Program entry point
;****************************************************************************

	assume	cs:_codeseg,ds:_data
_codeseg	segment	

	public	main
main proc	near

;
; Copy the real mode code and data to a conventional memory block, and init
; globals for conv mem block
;
	call	copy_real		; set up real mode code/data
	cmp	eax,TRUE		; exit if error
	je	#exit				;

;
; Save current real and prot mode IRQ4 vectors
;
	mov	ax,250Ch		; get IRQ4 interrupt number
	int	21h				;
	add	al,4				;
	mov	irq4_int,al			;
	mov	cl, irq4_int		; save real mode IRQ4 vector
	mov	ax, 2503h			; 
	int	21h				;
	mov	rm_irq4_vec, ebx		; 
	mov	ax, 2502h		; save prot mode IRQ4 vector
	mov	cl,irq4_int			;
	int	21h				; 
	mov	pm_irq4_sel, es			;
	mov	pm_irq4_off, ebx		; 

;
; Install our real-mode and protected-mode IRQ4 (COM1) handlers.
;
	push	ds			; set real and prot mode IRQ4 vectors
	mov	cl,irq4_int			;
	mov	bx,rm_csds			;
	shl	ebx,16				;
	lea	bx,rm_irq4_hnd			;
	lea	edx,pm_irq4_hnd			;
	mov	ax,cs				;
	mov	ds,ax				;
	mov	ax,2507h			;
	int	21h				;
	pop	ds				;

;
; Init MCR and IER in the 8250 serial chip. 
; Make sure IRQ4 is unmasked in the 8259 interrupt controller.
;
	mov	dx, COM_MCR		; set RTS, DTR, and OUT2 bits in the
	mov	al, 0Bh				; Modem Control Register
	out	dx, al				;
	mov	dx, COM_IER		; enable interrupts on receive data
	mov	al, 01h				; on the serial chip
	out	dx, al				;
	in	al, PIC_MSK		; unmask IRQ4 in the 8259 mask
	and	al, NOT IRQ4_MSK		; 
	out	PIC_MSK, al			;

;
; Prompt user to send serial data
;
	mov	ah, 09h			; print prompt message
	lea	edx, prompt_msg			;
	int	21h				;

;
; Loop, reading data from the buffer as it gets filled and printing
; it to the screen.  When we see a carriage return, exit the loop.
;
; Make a do-nothing DOS call inside the loop (get default disk) to
; allow DOS to generate a CTRL-C interrupt so the user can kill the
; program if they can't get serial hookup to work.
;
	mov	ax,SS_DOSMEM		; set ES:EBX to I/O data block in
	mov	es,ax				; conventional memory
	mov	ebx,iodat_offs			;
#loop:
	mov	ah,19h			; DOS call to allow CTRL-C
	int	21h				;
	mov	ecx,es:[ebx].RD_IDX	; keep looping if buffer empty
	cmp	ecx,es:[ebx].WR_IDX		;
	je	#loop				;
	pushfd				; get character and update read index,
	cli					; disabling interrupts while
	mov	dl,es:[ebx][ecx].IO_BUF		; we update the I/O globals
	inc	ecx				;
	cmp	ecx,IOBUF_SIZE			;
	jb	short #in_buf			;
	mov	ecx,0				;
#in_buf:					;
	mov	es:[ebx].RD_IDX,ecx		;
	popfd					;
	mov	ah, 02h			; print the character in DL on the
	int 21h					; display
	cmp	dl,CR			; continue loop unless it was CR
	jne	#loop				; 
	mov	dl,LF			; print a line feed
	mov	ah,2				;
	int	21h				;

;
; Restore serial port to previous state, restore IRQ4 handlers, and
; free up the conventional memory block we allocated.
;
	mov	dx, COM_MCR		; clear RTS, DTR, and OUT2 in the
	in	al, dx				; Modem Control Register
	and	al, 04h				; 
	out	dx, al				;
	mov	dx, COM_IER		; disable serial chip interrupts
	mov	al, 00h				; 
	out	dx, al				;
	in	al, PIC_MSK		; mask off IRQ4 in the 8259
	or	al, IRQ4_MSK			; 
	out	PIC_MSK, al			;
	push	ds			; restore original real and prot mode 
	mov	cl,irq4_int			; IRQ4 vectors
	mov	ebx, rm_irq4_vec		;
	mov	edx, pm_irq4_off		;
	mov	ax, pm_irq4_sel			; 
	mov	ds, ax				;
	mov	ax,2507h			;
	int	21h				;
	pop	ds				;
	mov	cx,cbuf_seg		; free up conventional memory block
	mov	ax,25C1h			;
	int	21h				;

#exit:
	mov	ax, 4C00h		; exit to DOS
	int	21h				;
main endp

;****************************************************************************
; COPY_REAL -- copy real mode code & data to a conventional memory block,
;	and initialize global data used to manage the memory block
;
; Returns:  TRUE	if error
;	    FALSE	if success
;****************************************************************************
	public	copy_real
copy_real proc	near
;
; Stack frame
;
#REAL_NBYTES equ (dword ptr 12[ebp])	; number of bytes RM code & data
#REAL_START equ	(dword ptr 8[ebp])	; start offset of RM code & data

	push	ebp			; set up stack frame
	mov	ebp,esp				;
	sub	esp,8			; allocate local data
	push	es			; save regs
	push	esi				;
	push	edi				;

;
; Allocate DOS segment in conventional memory the size of our real mode
; code and data.
;
; NOTE we are just assuming there is free conventional memory (the program
;	should have been linked with the -MINREAL switch), rather than
;	attempting to make sure memory is available with the 2525h and/or
;	2530h system calls as we would in a more elaborate program.
;
	lea	ebx, end_real		; end of RM code and data
	test	ebx,0FFFF0000h		; branch if not within 1st 64K of
	jnz	#bad_segorder			; of program segment
	lea	ecx, start_real		; start of RM code and data, rounded
	and	ecx, not 0Fh			; down to paragraph boundary
	mov	#REAL_START, ecx		; 
	sub	ebx, ecx		; RM code and data size in EBX
	mov	#REAL_NBYTES, ebx		; 
	add	ebx, 15			; round size up to paragraph count
	shr	ebx, 4				; 
	mov	ax, 25C0h		; alloc DOS memory block
	int	21h				; 
	jc	#no_mem			; branch if failure
	mov	cbuf_seg, ax		; save addr of conv mem block

;
; Calculate a real mode segment address for real mode CS and DS that will
; allow the real mode code to use all its link-time offsets to code and data.
; Copy the real mode code and data to conventional memory.
;
	mov	ax,cbuf_seg		; calculate real-mode CS and DS
	mov	ebx,#REAL_START			;
	shr	ebx,4				;
	sub	ax,bx				;
	mov	rm_csds,ax			;
	push	ds			; copy code & data to conv mem block
	mov	ecx, #REAL_NBYTES		;
	mov	ax, SS_DOSMEM			;
	mov	es, ax				;
	movzx	edi, cbuf_seg			;
	shl	edi, 4				; 
	mov	esi, #REAL_START		;
	mov	ax, cs				;
	mov	ds, ax				; 
	cld					;
	rep	movsb				; 
	pop	ds				;

;
; Calculate the protected mode offset in segment 0034h to the I/O data
; block we keep in conventional memory.  Also initialize the data block.
;
	lea	eax,io_data		; get prot mode offset to I/O data
	movzx	ebx,rm_csds			; in conv memory block
	shl	ebx,4				;
	add	eax,ebx				;
	mov	iodat_offs,eax			;
	mov	ax,SS_DOSMEM		; init to empty buffer
	mov	es,ax				;
	mov	eax,iodat_offs			;
	mov	es:[eax].RD_IDX,0		;
	mov	es:[eax].WR_IDX,0		;

	mov	eax,FALSE		; return success
#exit:
	pop	edi			; restore regs
	pop	esi				;
	pop	es				;
	mov	esp,ebp			; restore stack frame & exit
	pop	ebp				;
	ret					;

#no_mem:
;
; Couldn't allocate conventional memory for real mode code & data
;
_data	segment
nocmem_msg db	"Can't allocate conventional memory buffer",0Dh,0Ah,'$'
_data	ends
	mov	ah, 09h			; print err message
	lea	edx, nocmem_msg			;
	int	21h				;
	mov	eax,TRUE		; return error
	jmp	#exit				;

#bad_segorder:
;
; real mode code & data not within 1st 64K of program segment
;
_data	segment
boffs_msg db	'Real mode code/data > 64K into program segment',0Dh,0Ah,'$'
_data	ends
	mov	ah, 09h			; print err message
	lea	edx, boffs_msg			;
	int	21h				;
	mov	eax,TRUE		; return error
	jmp	#exit				;
copy_real endp

;****************************************************************************
; PM_IRQ4_HND - Protected Mode IRQ4 Interrupt Handler
;	Just reads the character from the serial port and stuffs it into
;	the circular buffer.
;****************************************************************************
	public	pm_irq4_hnd
pm_irq4_hnd	proc	near

	push	eax			; save regs we use
	push	ebx				;
	push	ecx				;
	push	edx				;
	push	es				;
	push	ds				;
	mov	ax,SS_DATA		; set DS to our data segment
	mov	ds, ax				; 
	mov	ax,SS_DOSMEM		; set ES:EBX to I/O data in 
	mov	es, ax				; conventional memory
	mov	ebx,iodat_offs			;

;
; Get the character and write it to the buffer, with interrupts still
; disabled so we can't get reentered.
;
; If the circular buffer is full, just drop this character on the floor.
;
	mov	dx, PORTNO		; read character from serial chip
	in	al, dx				;
	mov	ecx,es:[ebx].WR_IDX	; calculate new write index after
	inc	ecx				; we stuff this char into
	cmp	ecx,IOBUF_SIZE			; buffer
	jb	short #in_buf			;
	mov	ecx,0				;
#in_buf:					;
	cmp	ecx,es:[ebx].RD_IDX	; drop this char if buffer full
	je	short #done_ch			;
	mov	edx,es:[ebx].WR_IDX	; write this char to buffer
	mov	es:[ebx][edx].IO_BUF,al		;
	mov	es:[ebx].WR_IDX,ecx	; update write index
#done_ch:

;
; It's now OK to get reentered, so we can re-enable interrupts and 
; acknowledge the interrupt to the 8259
;
	sti				; re-enable interrupts
	mov	al, 20h			; send EOI to 8259
	out	PIC_EOI, al			;

	pop	ds			; restore registers
	pop	es				;
	pop	edx				;
	pop	ecx				;
	pop	ebx				;
	pop	eax				;
	iretd				; return to interrupted code

pm_irq4_hnd	endp

_codeseg	ends

;****************************************************************************
; RM_IRQ4_HND - Real Mode IRQ4 Interrupt Handler
;	Just reads the character from the serial port and stuffs it into
;	the circular buffer.
;****************************************************************************
	assume	cs:_rcodeseg, ds:dgroup
_rcodeseg segment 

	public	rm_irq4_hnd
rm_irq4_hnd	proc	near

	push	eax			; save regs we use
	push	ebx				;
	push	ecx				;
	push	edx				;
	push	ds				;
	mov	ax,cs			; set DS to our data segment
	mov	ds,ax				;

;
; Get the character and write it to the buffer, with interrupts still
; disabled so we can't get reentered.
;
; If the circular buffer is full, just drop this character on the floor.
;
	mov	dx, PORTNO		; read character from serial chip
	in	al, dx				;
	mov	ecx,io_data.WR_IDX	; calculate new write index after
	inc	ecx				; we stuff this char into
	cmp	ecx,IOBUF_SIZE			; buffer
	jb	short #in_buf			;
	mov	ecx,0				;
#in_buf:					;
	cmp	ecx,io_data.RD_IDX	; drop this char if buffer full
	je	short #done_ch			;
	mov	edx,io_data.WR_IDX	; write this char to buffer
	mov	io_data[edx].IO_BUF,al		;
	mov	io_data.WR_IDX,ecx	; update write index
#done_ch:

;
; It's now OK to get reentered, so we can re-enable interrupts and 
; acknowledge the interrupt to the 8259
;
	sti				; re-enable interrupts
	mov	al, 20h			; send EOI to 8259
	out	PIC_EOI, al			;

	pop	ds			; restore registers
	pop	edx				;
	pop	ecx				;
	pop	ebx				;
	pop	eax				;
	iret				; return to interrupted code

rm_irq4_hnd	endp

_rcodeseg ends

	end main
