;
;   Serial Port Code to be Injected Into a VMware Legacy BIOS File
;
;   (The Serial COM Output routines are those from Pete Batard's
;    assembly file found at: 
;    https://pete.akeo.ie/2011/06/crafting-bios-from-scratch.html)
;
;   Note: After assembling and editing NOPs as described in the
;         code below, this code must be injected into the BIOS
;         file at offsets 0x7D500 ff, and the jump instruction
;         at offsets 0x7E753 through 0x7E755 must be changed
;         from:  "E9 00 00"  to:  "E9 AA ED".

;   This code is easily assembled under MASM 6.15 (and likely some
;   other versions of MASM as well) using only "ml" (with no extra
;   switches). It will produce a .COM output file; in spite of the
;   warning message that the code does not begin at offset 0x100.


.286
.MODEL	TINY

.CONST

SUPERIO_BASE		equ 	2Eh	; WMware states that the "NS338 SIO chipset"
					; (PC97338 compatible) is supported.
PC97338_FER		equ	0	; PC97338 Function Enable Register
PC97338_FAR		equ	1	; PC97338 Function Address Register
PC97338_PTR		equ	2	; PC97338 Power and Test Register

COM_BASE		equ	3F8h	; Default COM1 Base after Super I/O Init.
COM_TB			equ	0    	; Transmit Buffer (W)
COM_BRD_LO		equ	0    ; Baud Rate Divisor LSB (when bit 7 of LCR is set)
COM_BRD_HI		equ	1    ; Daud Rate Divisor MSB (when bit 7 of LCR is set)
COM_IER			equ	1    ; Interrupt Enable Register
COM_FCR			equ	2    ; 16650 FIFO Control Register (W)
COM_LCR			equ	3    ; Line Control Register
COM_MCR			equ	4    ; Modem Control Registrer
COM_LSR			equ	5    ; Line Status Register


.CODE

ORG		0D500h

; F000:D500 is still where the first instruction of the injected code resides, but 
;           first, we need to save whatever the original BIOS code had in the AL 
;           register before moving on with sending that Diagnostic Code out to the
;           Serial Port, in other words:

;   The code here is for preserving the important bits of the AL register;
;     which holds the BIOS "Diagnostic Code" to be sent out a serial port!

		xor   al, 80h	; Sets only high nibble (high 4 bits of AL)
						; to zero (only if, AL is 80h to 8Fh; which
						;       I already discovered it would be!).
		or    al, 30h	; Turns AL into its ASCII equivalent for
						; 0 - 9, but would output the characters:
;						;    :   ;   <   =   >  ?   for 0A - 0F.

		mov   di, ax	; Store converted AL byte in lower half of
						; DI Register; ignoring whatever is in AH.
		mov   ax, cs
		mov   ds, ax	; Data Segment must be the same as the Code Segment.


;		cli				; Run code with interrupts disabled... not
						; necessary since we're already running the
						; BIOS code to get here.

		cld				; String manipulation direction set Forward.

init_superio:

		mov   dx, SUPERIO_BASE	;  The PC97338 datasheet says we are supposed
		in    al, dx			; to read this port twice on startup, but the
		in    al, dx			; VMware virtual chip doesn't seem to care...

; Send Super I/O chipset Configuration bytes from our Data Section:

		mov   si, offset superio_conf
		mov   cx, (serial_conf - superio_conf)/2

write_superio_conf:

		mov   ax, [si]

		mov   sp, rom1
		jmp	  superio_out

rom1:
		add   si, 2
		loop  write_superio_conf

init_serial:		; Initialize Serial Port using Serial Configuration bytes:

		mov   si, offset serial_conf
		mov   cx, (msg1 - serial_conf)/2

write_serial_conf:

		mov   ax, [si]

		mov   sp, rom2
		jmp   serial_out

rom2:
		add   si, 2
		loop  write_serial_conf

; Send our ASCII string out the Serial Port.

		mov   si, offset msg1

		mov   sp, rom3
		jmp   print_string		; Print msg1 string.

rom3:
		mov   ax, di	; Temporarily store altered VMware Diagnotic Code
						; in AL in the AH bytes, because AL must be used.

		mov   dx, COM_BASE + COM_LSR
		mov   ah, al
tx_stall:
		in    al, dx
		and   al, 20h	; Check that transmit register is empty.
		jz    tx_stall
		mov   dx, COM_BASE + COM_TB
		mov   al, ah		; Restore [AL] from AH.
		out   dx, al		; Send Altered Code Byte out Serial Port.

		mov   si, offset msg2	; Finish print line with msg2 string.
		
		mov   sp, rom4
		jmp   print_string		; Print msg2 string.

rom4:
		nop				; These 3 NOP instructions allow us
		nop				; to create an "E9" Near Jump here!
		nop				; They must be edited later to:

;		JMP   0E756h		; Return to hardcoded Address in
							; the official VMware BIOS Code!


;*******************************************************************************
; Subroutines:
;*******************************************************************************

superio_out:		; AL: Register Index,  AH: Data to Write.

		mov   dx, SUPERIO_BASE
		out   dx, al
		inc   dx
		xchg  al, ah
		out   dx, al
		jmp   sp

serial_out:		; AL: COM Register Index, AH: Data to Write.

		mov   dx, COM_BASE
		add   dl, al		; Unless something is wrong, we won't overflow to DH.
		mov   al, ah
		out   dx, al
		jmp   sp


putchar:			; AL: Character to Print.

		mov   dx, COM_BASE + COM_LSR
		mov   ah, al

tx_wait:

		in    al, dx
		and   al, 20h	; Check that transmit register is empty.
		jz    tx_wait
		mov   dx, COM_BASE + COM_TB
		mov   al, ah
		out   dx, al
		jmp   sp

print_string:		; SI (for LODSB): Offset to NUL terminated string.

		lodsb		; Load String Bytes from SI pointer.
		or    al, al		; Will keep sending characters unitl ...
		jnz   write_char	;    NUL (zero byte) is encountered.
		jmp   sp

write_char:

;		shl  esp, 10h	;  Possibly the only way left to
						; preserve SP contents since all
; the registers are in use - and they must be preserved too!
; (Pete Batard could have used either the BX or DI registers
; to save SP, but both are being used in this BIOS code!)


		NOP				; These NOP bytes must be changed to: 
		NOP				;       66 C1 E4 10
		NOP				;       SHL     ESP,10h
		NOP

		mov   sp, rom5
		jmp	  putchar

rom5:

; Restore saved SP register value.

		NOP				; These NOP bytes must be changed to: 
		NOP				;       66 C1 EC 10
		NOP				;       SHR     ESP,10h
		NOP

		jmp   print_string


.DATA

superio_conf	db   PC97338_FER, 15	; Enable COM, PAR and FDC
				db   PC97338_FAR, 16	; LPT=378, COM1=3F8, COM2=2F8
				db   PC97338_PTR, 0		; Make sure COM1 test mode is cleared

serial_conf		db   COM_MCR, 0			; RTS/DTS off, disable loopback
				db   COM_FCR, 7			; Enable & reset FIFOs. DMA mode 0.
				db   COM_LCR, 80h		; Set DLAB (access baudrate registers)
				db   COM_BRD_LO, 1		; Baud Rate 115200 = 0x0001
				db   COM_BRD_HI, 0
				db   COM_LCR, 3			; Unset DLAB. Set 8N1 mode

msg1	db	13, 10, "The VMware BIOS reached Diagnostic Code: 8", 0

msg2	db	"h (in its BB.ROM file).", 13, 10, 0


END
