HDK Technical Reference

Porting DOS inb and outb functionality

A frequent question is how to port a DOS application that calls the inb and outb instructions to read and/or write the registers of an application-specific device.

The solution is to break the original DOS program into two parts:

  1. The part that queries the user and makes the decision as to what should be turned on, turned off, or given new values. This part is implemented on UNIX systems as a standard user level application.

  2. The part that actually manipulates the hardware. This part is implemented on UNIX systems as a simple character driver that is composed of wrappers around the ``already modularized routines'' from the DOS code to perform the inb and outb operations.

WARNING: It is dangerous for user-level programs to do direct device I/O in the UNIX environment. One of the major risks is synchronization problems when accessing the hardware. The concern is that the inb or outb operation needs to be atomic, and it is harder to guarantee this from a user-level process than from kernel code. See ``Critical code section'' for more information about synchronization issues.

Calling inb and outb from a user-level program should be done only when testing devices that are unknown to the UNIX kernel (in other words, do not have a driver installed in the kernel). Do not access standard system devices (such as tapes, disks, and network adapter cards) in this way. Any direct user-level access of I/O addresses that are used by a kernel-level driver can corrupt the kernel, because the kernel's notion of the hardware state is no longer valid.

On SVR5, kdb has the ability to set breakpoints when someone does direct I/O access, which enables you to identify the %eip of a user-level process that may be causing problems.

For testing and migration purposes, the inb and outb instructions can be issued from a standard application on SCO UNIX systems.

  1. Code the sysi86 call in the application to gain I/O privleges.

    The SCO OpenServer 5 call to do this is:

       if (sysi86(SI86V86, V86SC_IOPL, 0x3000) < 0) {
       	perror ("Cannot enable direct I/O!");
    On SVR5 systems, the comparable call is:
       #include <sys/sysi86.h>
       #include <sys/inline.h>

    if (sysi86(SI86IOPL, 3) < 0) { perror ("Cannot enable direct I/O!"); exit(1);

    This can only be done with the native UDK compiler on SVR5 systems, not with gcc and other non-native compilers.

    If you are using the UODK to build a binary that works on both SVR5 and SCO OpenServer 5, you must get the values of the sysi86( ) arguments for both hosts and determine at runtime which arguments to pass to sysi86( ).

    On Linux, the comparable call is:

       #include <asm/io.h>
       if (iopl(3) < 0) {
       	perror ("Cannot enable direct I/O!");
    The Linux <asm/io.h> file contains inline asm functions that the system compiler uses to generate the appropriate instructions. The code must be compiled with optimization.

  2. Implement a small assembly language file with a name such as io386.s; an assembly language file must have a .s suffix. This file contains sequences for all the instructions that will be used.

  3. Compile this assembly code with the following command:
       as -o io386.o io386.s

  4. Link the resulting io386.o file with C programs that use these instructions and you have full access to the I/O commands.

The e3D sample driver that is included in the SCO OpenServer 5 ndsample package includes source code for the acfg user-level utility that illustrates how to implement this functionality.

On SCO OpenServer 5 systems, you can set up devices such as /dev/inb as an alternative implementation. This method incurs a fair amount of overhead so is not recommended when that is a problem, but for simple operations such as poking devices and checking status, this is useful.

  1. Create special device files, using the major number to match that of /dev/null. For example, if /dev/null uses major number 4:
       mknod c /dev/inb  4 3
       mknod c /dev/outb 4 3
       mknod c /dev/ind  4 5
       mknod c /dev/outd 4 5
       mknod c /dev/inw  4 4
       mknod c /dev/outw 4 4

  2. Call the open( ) system call to open the appropriate device.

  3. Call the lseek( ) system call to seek to the port number.

  4. Use the read( ) and write( ) system calls to poke the device or to read status information.

Be extremely careful about the operations that are done to /dev/inb and the related devices; commands such as dd, wc, and sum can hang the system and may even damage the hardware itself.

If performance is a concern and the application needs to read/write larger amounts of data or do frequent transfers, implementing an ioctl( ) operation that can transfer a string of bytes from user-level to kernel-level is more efficient than an implementation that incurs the overhead of repeated switches across the user/kernel boarder.

© 2005 The SCO Group, Inc. All rights reserved.
OpenServer 6 and UnixWare (SVR5) HDK - June 2005