serial.asm

back


; BSD 3-Clause License
; 
; Copyright (c) 2019, k4m1 <k4m1@protonmail.com>
; All rights reserved.
; 
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
; 
; * Redistributions of source code must retain the above copyright notice, 
;   this list of conditions and the following disclaimer.
; 
; * Redistributions in binary form must reproduce the above copyright notice,
;   this list of conditions and the following disclaimer in the documentation
;   and/or other materials provided with the distribution.
; 
; * Neither the name of the copyright holder nor the names of its
;   contributors may be used to endorse or promote products derived from
;   this software without specific prior written permission.
; 
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
; PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
; CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
; 

; ======================================================================== ;
; Initialize serial port, this is used by int 14h
;
;	DX = Device to initialize, should be figured out by irq handler
; 	AX = Baud rate divisor
; 	BL = Line Control Register value
;
; ======================================================================== ;
serial_init:
	push 	dx

	; first, disable interrupts
	inc 	dx
	xor 	al, al
	out 	dx, al

	; set DLAB = 1
	add 	dx, 2
	add 	al, 10000000b
	out 	dx, al

	; set baud rate divisor
	sub 	dx, 3
	out 	dx, al
	mov 	ah, al
	inc 	dx
	out 	dx, al

	; set line control
	mov 	al, bl
	add 	dx, 2
	out 	dx, al
	dec 	dx

	; enable fifo, clear w/ 14 byte threshold
	mov 	al, 0xC7 
	out 	dx, al

	pop 	dx
	ret

; ======================================================================== ;
;
; Requires:
;	si = offset to null-terminated string
; 		assuming segment f000
; Notes:
;	do check esi address, be careful with segmentation. :P
;
; ======================================================================== ;
serial_print:
	push 	si
	push	ax
	push	dx
	mov 	ax, ds
	push 	ax

	mov 	ax, 0xf000
	mov 	ds, ax

.print_start:
	mov	dx, 0x3F8
	.print_loop:
		lodsb
		test	al, al
		jz	.done
		call 	serial_wait_tx_empty
		jc 	.done
		out	dx, al
		jmp	.print_loop
	.done:
		pop 	ax
		mov 	ds, ax
		pop	dx
		pop	ax
		pop 	si
		ret

; ======================================================================== ;
; As simple serial print function, but used to print dynamic stuff from
; RAM instead of ROM, so DS != 0xF000 but set by user.
;
; Requires:
;	ds = offset to 0 terminated string
; 	ax = segment to 0 terminated string
;
; Returns:
;	nothing
;
serial_ram_print:
	; back up original data segment
	push 	bx
	mov 	bx, ds
	mov 	ds, ax

	push 	dx

	; print string from ds:si
	mov 	dx, 0x03F8
	.print:
		lodsb
		test 	al, al
		jz 	.done
		out 	dx, al
		jmp 	.print
	.done:
		pop 	dx
		mov 	ds, bx
		pop 	bx
		ret


; ======================================================================== ;
; Simple function to check that serial line is 'empty', we can 
; transmit byte
; (We read line status, and by 0x20 to check if tx is empty)
;
; We set carry flag on error.
;
serial_wait_tx_empty:
	push 	ax
	push 	cx
	mov 	cx, 50
	add 	dx, 5
	clc
	.loop:
		in 	al, dx
		and 	al, 0x20
		test 	al, al
		jz 	.done
		loop 	.loop
	stc
.done:
	sub 	dx, 5
	pop 	cx
	pop 	ax
	ret


; ======================================================================== ;
;
; Function to print raw hex value as ascii hex over
; serial port. 
;
; Requires:
;	ax = 16-bit integer to print
;
; ======================================================================== ;
serial_printh:
	push	dx
	push	ax
	push	bx
	push	cx

	mov	bx, ax
	xor	ax, ax
	mov	dx, 0x3F8
	mov	cx, 4

	.itoah:
		mov	al, bh
		and	al, 0xF0
		shr	al, 4
		shl	bx, 4

		cmp	al, 0x0A
		jl	.base10

	.base16:
		sub	al, 0x0A
		add	al, 0x41
		out	dx, al
		dec	cx
		jnz	.itoah

	.done:
		mov	al, 0x0A
		out	dx, al
		call 	serial_wait_tx_empty
		jc 	.end
		mov	al, 0x0D
		call 	serial_wait_tx_empty
		jc 	.end
		out	dx, al
	.end:
		pop	cx
		pop	bx
		pop	ax
		pop	dx
		ret

	.base10:
		add	al, 0x30
		out	dx, al
		dec	cx
		jnz	.itoah
		jmp	.done

; ======================================================================== ;
; Get serial line status, this may or may not be relevant here.
; Anyhow, int 14h, ah = 3 asks for status to be read to ah so here we go.
;
; requires:
;	DX = port to io base
; returns:
; 	AH = line status
;
serial_get_line_status:
	add 	dx, 5
	in 	al, dx
	sub 	dx, 5
	ret

; ======================================================================== ;
; Get modem status, used by int 14h ah = 3 aswell
serial_get_modem_status:
	add 	dx, 6
	in 	al, dx
	sub 	dx, 6
	ret
	
; ======================================================================== ;
; Helper function for BDA population to enumerate COM1-COM4
; This function uses stosw to store addresses, DI is *NOT* preserved.
;
serial_enum_devices:
	push 	ax
	push 	dx

	; we find serial devices by probing them via scratch register
	; (IO-base + 7, dlab = 0)
	mov 	dx, 0x03F8 + 7 		; COM1
	call 	__serial_port_enum_dev
	sub 	dx, 0x0100 		; COM2 (0x02F8)
	call 	__serial_port_enum_dev
	mov 	dx, 0x03E8 + 7		; COM3
	call 	__serial_port_enum_dev
	sub 	dx, 0x0100 		; COM4 (0x02E8)
	call 	__serial_port_enum_dev

	pop 	dx
	pop 	ax
	ret

; helper for serial_port_enum_devices
; requires:
; 	dx = port to probe + 7 (io base + offset to scratch reg)
; returns:
;	nothing
; sets:
;	word [di] = port if exists, 0 if failed or faulty, di += 2
;
__serial_port_enum_dev:
	mov 	ax, 0x4F4F 		; set ah to 4F for checking
	out 	dx, al  		; write 0x4F to scratch reg
	in 	al, dx 			; read back the scratch
	test 	ah, al 			; check if value read back is 4F
	jnz 	.no_port 		; if not, faulty or no device
	mov 	ax, dx 			; else, store device IO base
	sub 	ax, 7
.store:
	stosw
	ret
.no_port:
	xor 	ax, ax
	jmp 	.store