Jump to content

How to do port I/O (IN/OUT) from OS/2: Difference between revisions

From EDM2
No edit summary
mNo edit summary
Line 1: Line 1:
by [[Stefan Zollner]]
A question often asked in the comp.os.os2.programming.misc newsgroup is the following: How can I do port I/O (using the Intel IN/OUT commands) from within OS/2? There are several possibilities, but I have found the following most useful: Port I/O in OS/2 should be done from a 16-bit segment running at ring 2. Therefore, you need a 16-bit assembler (or compiler) and you have to tell the linker (in the DEF file) that the segment should have I/O priviledge. Once you have the source code, you need to assemble (or compile) the source code. The resulting OBJ file can either be linked directly to your main program, or you can link the OBJ file into a DLL. (That's what I do.) Then, your programs can do port I/O by simply linking to your DLL.
A question often asked in the comp.os.os2.programming.misc newsgroup is the following: How can I do port I/O (using the Intel IN/OUT commands) from within OS/2? There are several possibilities, but I have found the following most useful: Port I/O in OS/2 should be done from a 16-bit segment running at ring 2. Therefore, you need a 16-bit assembler (or compiler) and you have to tell the linker (in the DEF file) that the segment should have I/O priviledge. Once you have the source code, you need to assemble (or compile) the source code. The resulting OBJ file can either be linked directly to your main program, or you can link the OBJ file into a DLL. (That's what I do.) Then, your programs can do port I/O by simply linking to your DLL.


Line 339: Line 341:


'''DISCLAIMER:''' This sample code is for your information only. Do not attempt to use this code on your equipment without first inspecting the source code and making sure it does what you want. This code is supplied as is without warranty or liability of any kind. Most of the brand names on this page are registered trademarks.
'''DISCLAIMER:''' This sample code is for your information only. Do not attempt to use this code on your equipment without first inspecting the source code and making sure it does what you want. This code is supplied as is without warranty or liability of any kind. Most of the brand names on this page are registered trademarks.
[[Category:Driver Articles]]

Revision as of 05:21, 19 March 2012

by Stefan Zollner

A question often asked in the comp.os.os2.programming.misc newsgroup is the following: How can I do port I/O (using the Intel IN/OUT commands) from within OS/2? There are several possibilities, but I have found the following most useful: Port I/O in OS/2 should be done from a 16-bit segment running at ring 2. Therefore, you need a 16-bit assembler (or compiler) and you have to tell the linker (in the DEF file) that the segment should have I/O priviledge. Once you have the source code, you need to assemble (or compile) the source code. The resulting OBJ file can either be linked directly to your main program, or you can link the OBJ file into a DLL. (That's what I do.) Then, your programs can do port I/O by simply linking to your DLL.

Sample files:

Assembler source - io386.mac

; io386.mac
;
; This file was originally downloaded from the internet.
; It is now being used at the Ames Laboratory, Condensed Matter Physics
;
; Stefan Zollner, 12 March 1996
; History:
; 03/12/96 STZO Added lots of comments for PHY 232.
;
; This module contains 4 C or CPP callable procedures that allow access to the
; hardware ports. They must be defined in the calling program as follows:
;
; Note the qualifiers:
; _Far16 indicates a Far 16-bit procedure.
; Far means that the address to the routine is a segment:offset pointer.
; 16-bit means that parameters are either 16-bit constants (2 bytes)
; or far pointers (with segment:offset, needs 4 bytes on the stack).
; The _Pascal qualifier indicates that the PASCAL calling convention is
; used. Among other things, this reverses the order the parameters are
; passed on the stack.
;
; extern "C" void _Far16 _Pascal OUTP8(unsigned short, unsigned char);
; The first parameter is a port address, the second one is a byte.
; The byte is sent to the specified port using the OUT DX,AL instruction
;
; extern "C" void _Far16 _Pascal OUTP16(unsigned short, unsigned short);
; same as above, but a word is sent to the port using the OUT DX,AX instruction.
;
; extern "C" unsigned char _Far16 _Pascal INP8(unsigned short);
; The parameter is the port address. A byte is read using the IN AL,DX
; instruction and returned as the function result.
;
; extern "C" unsigned short _Far16 _Pascal INP16(unsigned short);
; Same as above, but a word is read using the IN AX,DX instruction.
; The word is returned as the function result.
;
; This module will compile under TASM (Borland assembler version 4.1 for OS/2)
; Use the /oi switch when using TASM and the IBM OS/2 linker
; 

IDEAL                   ;Enter Borland's IDEAL mode for TASM
P486N                   ;Assume we have an 80486 processor
MODEL OS2 FLAT          ;flat (OS/2) memory model 

SEGMENT R2SEG BYTE PUBLIC USE16 'CODE'
     ASSUME  CS:R2SEG, DS:NOTHING, ES:NOTHING
; This segment is defined as IOPL in the module definition (DEF) file.
;
; Detailed description:
;
; Port I/O is a priviledged instruction in the INTEL processor.
; Therefore, port I/O is not allowed in user programs (running in ring 3)
; We therefore need to place the routines containing port I/O statements
; and other priviledged instructions (such as disable/enable interrupts)
; into a special segment. We later tell the linker that this segment is
; supposed to run in ring 2 (see DEF file). In the design of the INTEL
; processor, every ring has its own stack. Therefore, some thunking needs
; to be done if you call ring 2 (16-bit) from ring 3 (32-bit). Therefore,
; calling these routines is rather slow. I have not tested the speed, but
; 400-kHz data acquisition is certainly not possible. (See IOAD.ZIP).
;
; R2SEG is simply the name of the SEGMENT. You can replace R2SEG with any
; valid identifier you like.
;
; The segment qualifiers mean the following: The segment is aligned on a
; BYTE boundary. The segment is PUBLIC (readable by other programs). The
; segment uses 16-bit assembly language instructions by default: Whenever
; there is an ambiguity, the 16-bit instructions will be put here by the
; assembler. However, you can override this with an operand size of address
; size instruction prefix (DB 66H or DB 67H). It is perfectly OK to include
; 32-bit instructions here. (See the IOAD example for analog/digital
; conversion.) This segment will be placed in the 'CODE' GROUP. 'CODE' not
; a reserved word, but most compilers generate this GROUP for their code
; segments.
;
; The ASSUME statements tells the assembler to assume that the segment
; register CS contains the address of the R2SEG segment. We assume nothing
; about the contents of DS and ES (which are set by the OS/2 operating system).
; We are not using variables here (which use the DS, ES segment registers),
; therefore this does not matter here. We also need to assume that the CS
; register points to the correct stack segment. This is set up by the operating
; system or the INTEL processor (not sure which). Remember that each ring
; has its own stack segment.
;
; References:
;
; 1) Borland Assembler (TASM) manual.
; 2) See my article in Computers in Physics, March/April 1994 (or is it 95).
; 3) Chapter 12 of the IBM Visual Age C++ (Version 3.0) compiler manual.
;    This chapter deals with 23-bit to 16-bit calling issues.
;    (or the manual for your compiler).
; 4) Chapter 3 of the IBM OS/2 Application Design Guide
;    (Mixing 16-bit and 32-bit code).
;
PUBLIC OUTP8, OUTP16, INP8, INP16
PROC OUTP8 FAR
 push  bp              ;save program's BP register
 mov   bp, sp          ;move 16-bit stack pointer into BP register
 mov   dx, [bp+8]      ;get port address (16-bit) from stack
 mov   al, [bp+6]      ;get data byte (8 bits) from stack
 out   dx, al          ;write data byte to port address in DX
 pop   bp              ;restore program's BP register
 ret   4               ;remove 4 bytes (2 words) from the stack, RETURN
ENDP OUTP8 

PROC OUTP16 FAR
 push  bp              ;same as above routine OUTP8
 mov   bp, sp
 mov   dx, [bp+8]
 mov   ax, [bp+6]      ;but now we load a data word (16 bits) into AX
 out   dx, ax          ;write data word in AX to port address in DX
 pop   bp
 ret   4            ;remove 4 bytes (2 words) from the stack
ENDP OUTP16

PROC INP8 FAR
 push  bp           ;same as OUTP8, but now we only have one parameter
 mov   bp, sp
 mov   dx, [bp+6]   ;get port address and store it in DX
 in    al, dx       ;read a byte from the port in DX and store it in AL
 sub   ah, ah       ;set the high byte AH to zero
 pop   bp
 ret   2            ;remove 2 bytes (1 word) from the stack
ENDP INP8

PROC INP16 FAR       ;same as INP16, but now read 16-bit word
 push  bp
 mov   bp, sp
 mov   dx, [bp+6]
 in    ax, dx       ;read a 16-bit word from port in DX and store it in AX
 pop   bp
 ret   2            ;remove 2 bytes (1 word) from the stack
ENDP INP16

ENDS R2SEG           ;end of the ring 2 segment
  END               ;end of the assembly language program

Include file for C++ main program - io386.hpp

/* io386.hpp                                              */
/* external function declarations for io386.dll/lib       */
/* to be used in calling routine                          */
/*                                                        */ 

/* Warning: If you include this file in a C program (not C++),       */
/* then error messages may be caused by the extern "C" modifier.     */
/* When including this in a C program, replace extern "C" by extern. */

extern "C" void _Far16 _Pascal OUTP8(unsigned short, unsigned char);
// The first parameter is a port address, the second one is a byte.
// The byte is sent to the specified port using the OUT DX,AL instruction

extern "C" void _Far16 _Pascal OUTP16(unsigned short, unsigned short);
// same as above, but a word is sent to the port using the OUT DX,AX instruction. 

extern "C" unsigned char _Far16 _Pascal INP8(unsigned short);
// The parameter is the port address. A byte is read using the IN AL,DX
// instruction and returned as the function result.

extern "C" unsigned short _Far16 _Pascal INP16(unsigned short);
// Same as above, but a word is read using the IN AX,DX instruction.
// The word is returned as the function result.

Include file for C main program - io386.h

/* io386.hpp                                              */
/* external function declarations for io386.dll/lib       */
/* to be used in calling routine                          */
/*                                                        */

extern void _Far16 _Pascal OUTP8(unsigned short, unsigned char);
// The first parameter is a port address, the second one is a byte.
// The byte is sent to the specified port using the OUT DX,AL instruction

extern void _Far16 _Pascal OUTP16(unsigned short, unsigned short);
// same as above, but a word is sent to the port using the OUT DX,AX instruction.

extern unsigned char _Far16 _Pascal INP8(unsigned short);
// The parameter is the port address. A byte is read using the IN AL,DX
// instruction and returned as the function result.

extern unsigned short _Far16 _Pascal INP16(unsigned short);
// Same as above, but a word is read using the IN AX,DX instruction.
// The word is returned as the function result.

Object code.

File:Io386.obj

Linker definition file for DLL (DEF) - io386.def

LIBRARY IO386 INITINSTANCE TERMINSTANCE
PROTMODE
DATA MULTIPLE NONSHARED READWRITE LOADONCALL
CODE LOADONCALL
DESCRIPTION 'Port I/O 16-bit library, Version 1.02, 02/16/96 Kurt Jensen/SZ/KM'
SEGMENTS R2SEG CLASS 'CODE' IOPL
STACKSIZE 65535
EXPORTS
;From object file:  F:\SPEA300\queues\indxlib\io386\io386.obj
 ;PUBDEFs (Symbols available from object file):
 ;The number of words passed on the stack has to added by hand.
 ;This is NOT added by CPPFILT
   INP16   1
   OUTP8   2
   OUTP16  2
   INP8    1

Resulting DLL

File:Io386.dll

Resulting import library (LIB)

File:Io386.lib

The make file which builds it all - io386.mak

# io386.mak
# Created by IBM WorkFrame/2 MakeMake at 19:07:20 on 17 Feb 1996
#
# The actions included in this make file are:
#  Assemble::Borland Assembler
#  Compile::C++ Compiler
#  Link::Linker
#  Package::Zip
#  Lib::Copy DLL to LIBPATH
#  Lib::Import Lib

.SUFFIXES: .LIB .cpp .dll .jnk .mac .obj  

.all: \
   .\io386.zip \
   .\io386.jnk \
   .\io386.LIB

.mac.obj:
   @echo " Assemble::Borland Assembler "
   F:\BCOS2\BIN\TASM.EXE %s /oi

{F:\os2utils\tcpip\wwwfemto\os2\io386}.mac.obj:
   @echo " Assemble::Borland Assembler "
   F:\BCOS2\BIN\TASM.EXE %s /oi

.cpp.obj:
   @echo " Compile::C++ Compiler "
   icc.exe /Q /Wall /Gh /Ti /Gm /Gd /Ge- /G4 /Ft- /C %s

{F:\os2utils\tcpip\wwwfemto\os2\io386}.cpp.obj:
   @echo " Compile::C++ Compiler "
   icc.exe /Q /Wall /Gh /Ti /Gm /Gd /Ge- /G4 /Ft- /C %s

.dll.jnk:
   @echo " Lib::Copy DLL to LIBPATH "
   CMD.EXE /C copy %|fF.dll f:\myos2\dll

{F:\os2utils\tcpip\wwwfemto\os2\io386}.dll.jnk:
   @echo " Lib::Copy DLL to LIBPATH "
   CMD.EXE /C copy %|fF.dll f:\myos2\dll

.dll.LIB:
   @echo " Lib::Import Lib "
   implib.exe /nologo %|dpfF.LIB %s

{F:\os2utils\tcpip\wwwfemto\os2\io386}.dll.LIB:
   @echo " Lib::Import Lib "
   implib.exe /nologo %|dpfF.LIB %s

.\io386.dll: \
   .\IO386.obj \
   .\dllinit.obj \
   {$(LIB)}cppopa3.obj \
   {$(LIB)}io386.def \
   io386.mak
   @echo " Link::Linker "
   icc.exe @<<
    /B" /de /nologo /noe /m /l"
    /Feio386.dll 
    cppopa3.obj 
    io386.def
    .\IO386.obj
    .\dllinit.obj
<<

.\io386.zip: \
   .\io386.dll \
   io386.mak
   @echo " Package::Zip "
   cmd.exe /C f:\os2utils\unz50x32\zip -9 io386 * -x *.zip

.\IO386.obj: \
   F:\os2utils\tcpip\wwwfemto\os2\io386\IO386.mac \
   io386.mak

.\dllinit.obj: \
   F:\os2utils\tcpip\wwwfemto\os2\io386\dllinit.cpp \
   io386.mak

.\io386.jnk: \
   .\io386.dll \
   io386.mak

.\io386.LIB: \
   .\io386.dll \
   io386.mak

A zip file containing all of the above, plus additional files you need.

File:Io386.zip

Current version: 1.02, dated 02/16/1996.

Tools

Tools you need: I use the following programming tools for doing port I/O:

  • VisualAge C++ for OS/2 (version 3.0) with CSDs.
  • Borland Assembler TASM (version 4.1), included in Borland C++ for OS/2, version 1.5.

DLLINIT.CPP

Remarks: I was unable to create a DLL consisting ENTIRELY of routines written in assembly language. Therefore, I created a dummy C++ file (DLLINIT.CPP) which is supposed to initialize the C++ runtime environment.

 /* This dummy routine is needed to initialized the C/C++ runtime environment */
 /* in the DLL. Without this dummy function, the NMAKE will fail with an */
 /* error message LNK4038: No starting address. */
 
 int dummy;

Please note: Dr.-Ing. Holger Veit has pointed out to me that there is a different way to do port I/O without requiring ring switching and going to 32-bit. He recommended to use device driver for port I/O. I have not tried this and do not intend to make information about this method available. I merely want to disperse Dr. Veit's opinion that a 16-bit segment is NOT required for port I/O (although I find it convenient for my own purposes). Dr. Veit suggests to download xf86s363.zip from HOBBES and use the FASTIO$ device driver for port I/O. Also see Dr. Veit's article on 32-bit port I/O in the EDM/2 magazine (volume 4, number 1; January 1996).

Also, Eberhard Mattes from Stuttgart has recommended to me that I should stay away from calls to ring 2 DLLs in order to get better performance. He suggests that I should use EMXIO.DLL, or, for even better performance, xf86.sys (sp?).

I respect the opinions of these gentlemen (they are probably right), but what I describe here works just fine for me, therefore I keep using my ring 2 DLLs.

Acknowledgment: IO386.ZIP was originally downloaded from the internet. The original version was written by Keith Murray (Oregon State) with help from Joel Armengaud (IBM). We have modified the file and added some of our own routines. We also added documentation. This is a link to the original documentation which we downloaded from hobbes.

Disclaimer: This server is an experimental offering. Please send comments or complaints to Dr. Stefan Zollner, Department of Physics and Astronomy, A205 Physics Hall. zollner@iastate.edu

DISCLAIMER: This sample code is for your information only. Do not attempt to use this code on your equipment without first inspecting the source code and making sure it does what you want. This code is supplied as is without warranty or liability of any kind. Most of the brand names on this page are registered trademarks.