3. Accessing ports with dosemu

This section written by Alberto Vignani <vignani@mbox.vol.it> , Aug 10, 1997 and updated by Bart Oldeman, June 2003.

3.1. General

For port I/O access the type ioport_t has been defined; it should be an unsigned short, but the previous unsigned int is retained for compatibility. Also for compatibility, the order of parameters has been kept as-is; new code should use the port_real_xxx(port,value) function. The new port code is selected at configuration time by the parameter

    	--enable-new-port
which will define the macro NEW_PORT_CODE. Files: portss.c is now no more used and has been replaced by n_ports.c; all functions used by 'old' port code have been moved to ports.c. Note that the code in portss.c (retrieved from Scott Buchholz's pre-0.61) was previously disabled (not used), because it had problems with older versions of dosemu.

The rep;in,out instructions will been optimized so to call iopl() only once.

3.2. Port I/O access

Every process under Linux has a map of ports it is allowed to access. Too bad this map covers only the first 1024 (0x400) ports. For all the ports whose access permission is off, and all ports over 0x400, an exception is generated and trapped by dosemu.

When the I/O permission (ioperm) bit for a port is ON, the time it takes to access the port is much lower than a microsecond (30 cycles on a P5-150); when the port is accessed from dosemu through the exception mechanism, access times are in the range of tenths of us (3000 cycles on the P5-150) instead. It is easy to show that 99% of this time is spent in the kernel trap and syscalls, and the influence of the port selection method (table or switch) is actually minimal.

There is nothing we can do for ports over 0x400, only hope that these slow access times are not critical for the hardware (generally they are not) and use the largest possible word width (i.e. do not break 16- and 32-bit accesses).

The 'old' port code used a switch...case mechanism for accessing ports, while now the table access (previously in the unused file portss.c) has been chosen, as it is much more clear, easy to maintain and not slower than the "giant switch" method (at least on pentium and up).

There are two main tables in ports.c:

It works this way: when an I/O instruction is trapped (in do_vm86.c and dpmi.c) the standard entry points port_in[bwd],port_out[bwd] are called. They log the port access if specified and then perform a double indexed jump into the port table to the function responsible for the actual port access/emulation.

Ports must be registered before they can be used. This is the purpose of the port_register_handler function, which is called from inside the various device initializations in dosemu itself, and of port_allow_io, which is used for user-specified ports instead. Some ports, esp. those of the video adapter, are registered an trapped this way only under X, while in console mode they are permanently enabled (ioperm ON). The ioperm bit is also set to ON for the user-specified ports below 0x400 defined as fast. Of course, when a port has the ioperm bit ON, it is not trapped, and thus cannot be traced from inside dosemu.

There are other things to consider:

3.2.1. System Integrity

To block two programs from accessing a port without knowing what the other program does, this is the strategy dosemu takes:

  • If the port is not listed in /proc/ioports, no other program should access the port. Dosemu will register these ports. This will also block a second dosemu-process from accessing these ports. Unfortunately there is no kernel call yet for registering a port range system-wide; see later for current hacks.

  • If the port is listed, there's probably a device that could use these ports. So we require the system administrator to give the name of the corresponding device. Dosemu tries to open this device and hopes this will block other from accessing. The parallel ports (0x378-0x37f) and /dev/lp1 act in this way.

    To allow access to a port registered in /proc/ioports, it is necessary that the open on the device given by the system administrator succeeds. An open on /dev/null will always succeed, but use it at your own risk.

3.2.2. System Security

If the strategy administrator did list ports in /etc/dosemu.conf and allows a user listed in /etc/dosemu.users to use dosemu, (s)he must know what (s)he is doing. Port access is inherently dangerous, as the system can easily be screwed up if something goes wrong: just think of the blank screen you get when dosemu crashes without restoring screen registers... As an extra precaution you have to use the "-s" dosemu command line switch so you won't access the hardware directly or use console graphics by accident.

3.2.3. The port server

Starting with version 1.1.4.5 the exception mechanism uses a port server. If any slow ports from $_ports, any ports above 0x3ff (depending on the Linux kernel, including some video cards and $_pci), or the native speaker are selected (timer 2 of port 0x43 cannot be fast), DOSEMU will fork. The main DOSEMU will then drop its root privileges and communicates via pipes with the (forked) privileged port server. The server then checks if it is allowed to access the port and acts appropriately. This way it is impossible for a DPMI program to manipulate any forbidden ports (separate address spaces). Fortunately the overhead of pipes and process switching seems to be negligible compared to the time it takes to trap the port access.

If the speaker is emulated and all ports are "fast", or if DOSEMU is non-suid-root and run by a normal user, then the above forking is unnecessary and does not occur.