# lptcons.S - Parallel port utility.
#
# Simple program that provides a prompt and allows the user to
# type in a number from 0 - 255.  A byte of data representing that
# number is sent through the parallel port.  This program assumes
# that the first parallel port is at 0x378 because we are running
# under a protected mode environment and cant access the bios data area
# to find out.  We COULD read /proc but oh well
#
# You can enter data in hexadecimal too with the formax 0xXX where XX
# is the number in hex.  The conversion isn't elegant, but works
#
# By Dennis Katsonis
# March 2009
#
# More an excercise in Assembly programming.  Not the best
# coding, but it serves the purpose.
#
# v1.1 - 26 Nov 11 - Added support for hexadecimal input
	
.MACRO PRINT message length

	movl $4, %eax
	movl $1, %ebx
	movl \message, %ecx
	movl \length, %edx
	int $0x80
.ENDM

.MACRO EXIT
	movl $1, %eax
	movl $0, %ebx
	int $0x80
.ENDM


.section .data
.equ PORT, 0x378
.equ IOPERM, 101
.equ GETUID, 24
.equ SETUID, 23
.equ QUITVALUE, 0x2000
.equ INVALIDVALUE, 0x4000
.equ HELPVALUE, 0x1000

intro:
	.ascii "	**** LPT Command Console ****\nUse 'q' to exit program, 'h' for help.\n"
	.ascii "Version 1.1\n\n"
intro_end:
	.equ intro_len, intro_end - intro

error_msg:
	.ascii "Could not open port.\n"
	.ascii "You may need to run as root, or setuid this executable binary to root.\n"
error_msg_end:
	.equ error_msg_len, error_msg_end - error_msg

priv:	
	.ascii "Could not drop root priveledges.\n"
priv_end:
	.equ priv_len, priv_end - priv
prompt:
	.ascii "LPT:> "
prompt_end:
	.equ prompt_len, prompt_end - prompt
quit:
	.ascii "Exiting...\n"
quit_end:
	.equ quit_len, quit_end - quit

invalid_entry:
	.ascii "Invalid entry.  Please under number from 0 - 255/0x0 - 0xFF.\n"
invalid_entry_end:
	.equ invalid_entry_len, invalid_entry_end - invalid_entry
help:
	.ascii "Enter a number from 0 - 255.  A single byte of data\n"
	.ascii "representing that value will be sent over the parallel\n"
	.ascii "port.  Prefix hexadecimal numbers with '0x'.\n"
	.ascii "Enter q to exit the program.\n"
	.ascii "\nBy Dennis Katsonis, March 2009\nDK SOFT 2009\n"
help_end:
	.equ help_len, help_end - help
	
.section .bss

.lcomm buffer, 8

.section .text
.globl _start

_start:
	PRINT $intro $intro_len
	movl $IOPERM, %eax
	movl $PORT, %ebx
	movl $4, %ecx
	movl $3, %edx
	int $0x80
	test %eax, %eax
	jnz error
			# Now that we have enabled the ports, we'll
			# drop root priveleges.
	movl $GETUID, %eax
	int $0x80	# get the user ID
	movl %eax, %ebx
	movl $SETUID, %eax
	int $0x80	# set user ID to original user
	cmp $-1, %eax
	jne loop2
	PRINT $priv $priv_len
	
loop2:			# main program loop	
	PRINT $prompt $prompt_len
	call ReadInt32
	test $QUITVALUE, %eax
	jnz end_prog
	test $HELPVALUE, %eax
	jnz disp_help	
	test $INVALIDVALUE, %eax
	jnz l2	# in case of any other invalid entry, print message
		# Now we have eliminated all invalid entries
		# the data we want to send is already in the
		# AL register.
	
	movl $PORT, %edx
	out  %al, %dx	
	jmp loop2
l2:
	# no point preserving the invalid entry
	PRINT $invalid_entry $invalid_entry_len
	jmp loop2 
disp_help:
	PRINT $help $help_len
	jmp loop2
end_prog:
	PRINT $quit $quit_len
	EXIT
error:
	PRINT $error_msg $error_msg_len
done:
	EXIT


.type ReadInt32, @function
ReadInt32:
	movl $3, %eax
	movl $0, %ebx
	movl $buffer, %ecx
	movl $8, %edx
	int $0x80 	# read text from keyboard
	cmp $-1, %eax	# return if error
	je return_error
	cmp $1, %eax	# If there is no input, error
	je return_error
	movl %ecx, %esi # point ESI to buffer, which is already in ECX
	movl %eax, %edi # EDI has number of letters recieved
	xor %eax, %eax
	xor %edx, %edx
	movb (%esi), %dl
	cmp $'0', %edx
	jne base10
	inc %esi
	movb (%esi), %dl
	cmp $'x', %edx
	jne base10
	inc %esi
	movl $16, %ebx # radix multiplier for Hexadecimal
	jmp loop1
base10:
	movl $10, %ebx # radix multiplier
loop1:
	movb (%esi), %dl # move character to register DL
	cmp $'h', %edx	# See if it is an 'h'
	je h0
	cmp $'H', %edx	# See if it is an 'h'
	je h0
	cmp $'?', %edx 	# Or a question mark
	je h0
	cmp $'q', %edx	# or a 'q'
	je q0
	cmp $'Q', %edx
	je q0		# Did the user quit?
	cmp $0x0A, %edx	# Or have we hit the "ENTER" key?
	je return
	cmp $0, %edx	# Or is it the end of an asciz string?
	je return
	cmp $'0', %edx  # check to see if its a number
	jl return_error # There are no legitimate ASCII chars below this
	cmp $10, %ebx
	je base10conv
	cmp $'9', %edx	# is it a number?
	jle hexcheck2   # if not, its a letter, lets see if its
			# within letter range.
	cmp $'f', %edx
	jg return_error
	cmp $'A', %edx
	jl return_error
	cmp $'a', %edx
	jge hexcheck1
	cmp $'F', %edx
	jg return_error
	jmp hexcheck3
hexcheck1:	
	sub $39, %edx
	jmp hexcheck2
hexcheck3:	
	sub $7, %edx
	jmp hexcheck2
base10conv:	
	cmp $'9', %edx
	jg return_error
hexcheck2:	
	sub $48, %edx 	# Subtract 0x30 so we convert the ASCII value
			# of the digit to the digits value itself.
	mul %bl
	jo return_error	# We've overflowed.  As we are multiplying ONLY
			# the AL register, if we exceed 255, then we will have a
			# legitimate result in AX, but next time this runs
			# we will only multiply the lower 8 bits 
			# (the AL portion) thereby making it possible to
			# return a figure between 0 - 255
			# even though we've entered 'invalid' data.
			# This ensures that as soon we've exceeded 8 bits,
			# we bail out.
	add %edx, %eax	# Add to total
	inc %esi	# Go to next character
	cmp %edi, %esi	# End of buffer?
	jne loop1
	ret
h0:
	movl $HELPVALUE, %eax
	ret
q0:
	movl $QUITVALUE, %eax 
	ret
return_error:
	movl $INVALIDVALUE, %eax
return:
	ret

