IOport : Access Input/Output ports in Mocka executables

As a true hacking geek, I needed to access the I/O ports of the PC. Under DOS that was no big deal, but under Linux it's different. I know I/O ports should be shielded most of the time by the operating system, but there are times that you simply need to flip some switches.

Switches on the LPT port (aka Geek Port) to be precise. To get all those projects working from our DOS era's.
So I decided it was time to bend some rules and extend Mocka with a small library that enables direct port access from within Mocka executables.

This library goes down to the bare bones of the computing platform. I work on a PC running Slackware Linux. So that's the hardware I will support and since I know my way with assembly language, I made the actual interface code in AT&T style 80386 code.

The library consists of two parts:

IOport.md is the Modula-2 DEFINITION MODULE in which we describe to the compiler and the user how the syntax is and which parameters are passed between modules.
IOport.asm is the actual implementation of the library. It is 100% assembly language, made with the (free) GNU AS assembler.

In order to test what I just made, I needed to make a small program. This is what is included as 'prf'. It checks the workings of the module.

IOport.md, the FOREIGN MODULE.

FOREIGN MODULE IOport;

(*  This module supports IO port access under Mocka / Linux
    CopyLeft Jan 2004, Jan Verhoeven.
    
    This module is published under de rules of the GNU GPL.
    
    To change the working of this material do as follows:
    
    $ jed IOport.asm
    $ as IOport.asm -o IOport.o
    
    and you get an improved version. Improvements and normal use
    are entirely on your own risk.

    Please feel free to improve this software, if needed. You may even
    port this source to a lesser language like C.
    This software comes without any warranty of any kind.    *)
		     
(* 01 : Get a working MODULE, no frills.                  Jan 02, 2004 *)


PROCEDURE InPort (port  : CARDINAL) : CARDINAL;

	  (* Read a byte from I/O address 'port' and return it to the caller *)


PROCEDURE OutPort (port, value  : CARDINAL);

	  (* Write a byte 'value' to I/O address 'port' *)


PROCEDURE IOperm (from, to   : CARDINAL; value   : BOOLEAN) : BOOLEAN;

	  (* Ask unix permission to access the specified I/O addresses:
              from = first port to access,
                to = last port to access,
             value = TRUE or FALSE.
	     
	     The values of the parameters 'from' and 'to' are inclusive.
	     So the snippet:
	     
	     IF IOperm (0x378, 0x37A, TRUE) = FALSE THEN
	        WriteString ('Could not get access to I/O ports.');
		WriteLn
	     END;
	     
	     asks the operating system to access ports 0x378, 0x379 and
	     0x37A. If the caller has enough privileges, the access is
 	     granted and the IF statement is not executed. 
	     *)

END IOport.
   
This was the easy part. Now for something completely different.

IOport.asm, the assembly source.

What follows is the assembly language sourcecode for the interface routines. These are all rather straighforward for all people who have ever tried to do something similar under DOS.
The main difference is the (at first) odd syntax of the AS assembler. But the syntax is not THAT odd. It's just a lot like Modula-2.... All registernames must be prefixed by a percent sign (%). All data values must be prefixed by a dollar sign ($). But that's all done to protect the programmer against his or her own speed.

The main problem is the reversed operand order. We all got used to the Intel syntax which is completely erratic. And now, if we go to the AT&T syntax (which is logical), we have problems since we got used to a bad predecessor system....

See for yourself what I mean. If you don't like this syntax, use NASM. Everything following a '#' symbol is a comment (until the end of the line).

# IMPLEMENTATION MODULE PortIO;

         .globl   InPort
	 .globl   OutPort
	 .globl	  IOperm

	 .text
	 .align   4

# PROCEDURE InPort (port  : CARDINAL) : CARDINAL;

InPort:
	 pushl	  %ebp			# stack up EBP
	 movl     %esp, %ebp		# EBP := ESP
	 movl	  8(%ebp), %edx		# EDX := port
	 movl	  $0000, %eax		# EAX := 0
	 inb  	  %dx, %al		# EAX := port value
	 movl	  %ebp, %esp		# ESP := EBP
	 popl	  %ebp			# restore old value of EBP
	 ret


# PROCEDURE OutPort (port, value  : CARDINAL);

OutPort:
	 pushl	  %ebp
	 movl	  %esp, %ebp		# set up EBP
	 movl	  8(%ebp), %edx		# EDX := port
	 movl	  12(%ebp), %eax       	# EAX := value
	 outb	  %al, %dx  		# write to I/O port
	 movl	  %ebp, %esp
	 popl	  %ebp
	 ret


# PROCEDURE IOperm (from, to   : CARDINAL; value   : BOOLEAN) : BOOLEAN;

IOperm:
       	 pushl	  %ebp
	 movl     %esp, %ebp		# save EBP and set up new EBP
	 movl	  8(%ebp), %ebx		# EBX := first port
	 movl	  12(%ebp), %ecx	# ECX := last port
	 subl	  %ebx, %ecx		# ECX := ECX - EBX + 1
	 incl	  %ecx	    		# ECX := nr of ports
	 jecxz	  IOpermError
	 movl	  16(%ebp), %edx	# EDX := flag
	 movl	  $101, %eax		# EAX := code for IOperm call
	 int	  $0x80	    		# perform SYSTEM call
	 movl	  $0001, %eax		# EAX := 0001
	 sbbl	  $0000, %eax		# IF Error THEN  EAX := 0000
	 jmp	  IOpermExit
	 
IOpermError:
	 movl	  $0000, %eax

IOpermExit:
	 movl	  %ebp, %esp
	 popl	  %ebp
	 ret
   
You assemble the source with the command:
   bash-2.05$ as IOport.asm -o IOport.o
   
With the 0608m compiler: copy the object file to the m2bin subdirectory. That's it. Commence now as usual:
The first time the library is used by the compiler, it sees that there was an update and it will automatically recompile the sources to make the Mocka specific files.

At this moment I feel tempted to make more libraries in pure assembly language. With a list of some Linux Systemcalls (using INT 0x80), the full InOut library could be rewritten to snugly fit the PC hardware. And to come close to FST Modula-2.

Any volunteers?

prf.mi, the test software.

What follows is a typical testing program. It does nothing sensible and it is littered with WriteString statements.
In first instance this program was called 'test.mi' and the executable was called 'test'. This caused a lot of confusion since there is a Linux program called 'test' as well and it is in a directory which is early in the searchpath. So every time I wanted to try my 'test' program, the other one was invoked.

So if you make a 'test' program under Linux do NOT call it 'test'!

MODULE prf;

FROM  IOport    IMPORT  InPort, OutPort, IOperm;
FROM  InOut  	IMPORT	WriteString, WriteLn, WriteCard, WriteBf;

VAR  ioPerm	: BOOLEAN;
     ioVal	: CARDINAL;

BEGIN
   ioPerm := FALSE;
   WriteString ('Start....');
   WriteLn;
   WriteString ('ioPerm = ');
   IF  ioPerm = TRUE  THEN
      WriteString ('TRUE.')
   ELSE
      WriteString ('FALSE.')
   END;
   WriteLn;
   WriteBf;
   ioPerm := IOperm (378H, 37AH, TRUE);
   IF ioPerm = FALSE  THEN
      WriteString ('No permission to access ports 378 - 37A.');
      WriteLn;
      HALT
   ELSE
      WriteString ('Permission received to access ports 378 - 37A.');
      WriteLn
   END;
   WriteString ('ioPerm = ');
   IF  ioPerm = TRUE  THEN
      WriteString ('TRUE.')
   ELSE
      WriteString ('FALSE.')
   END;
   WriteLn;
   WriteString ('We have executed the IOperm function and are out of it again.');
   WriteLn;
   WriteString ('Coming up now: OutPort and InPort and the WriteCard.');
   WriteLn;
   WriteBf;
   OutPort (378H, 1);
   ioVal := InPort (378H);
   WriteCard (ioVal, 4);
   WriteLn;
   WriteString ('The WriteCard has executed. Time to redeem IOperm.');
   WriteLn;
   WriteString ('ioPerm = ');
   IF  ioPerm = TRUE  THEN
      WriteString ('TRUE.')
   ELSE
      WriteString ('FALSE.')
   END;
   WriteLn;
   WriteBf;
   IF  ioPerm = TRUE  THEN
      WriteString ("ioPerm = TRUE and we're gonna give it back.");
      WriteLn;
      WriteBf;
      ioPerm := IOperm (378H, 37AH, FALSE);
      IF  ioPerm  = FALSE  THEN
         WriteString ('Could not return I/O privileges.');
	 WriteLn;
	 WriteString ('Aborting.');
	 WriteBf;
	 HALT
      ELSE
         WriteString ('I/O privileges successfully returned.');
	 WriteLn
      END;
      WriteBf
   ELSE
      WriteString ('If this message is printed, something fishy is going on.');
      WriteLn
   END;
   WriteBf
END prf.
   
OK, lads, now download and use it.

Oh, I almost forgot: you need to give your program enough privileges, otherwise it will be denied to call the IOperm system call. After compiling your source, you must become 'root' and then enter the following magic spells:

   bash-2.05# chown root.root prf
   bash-2.05# chmod 4755 prf
   
A little swearing is allowed but a magic wand is an accessory and it will cost you extra. The wand is not covered by the GNU GPL, contrary to the sources you saw until now on this page.

If you forget to chown and chmod the executable you end up with this:

   jan@beryllium:~/modula/lib$ ./prf
   Start....
   ioPerm = FALSE.
   Permission received to access ports 378 - 37A.
   ioPerm = TRUE.
   We have executed the IOperm function and are out of it again.
   Coming up now: OutPort and InPort and the WriteCard.
   Segmentation fault
   
Forget either of the two and you get SegFaults by the dozen...

Page created 8 August 2004,