...making Linux just a little more fun!
Hardware I/O is important to quite a few people but the traditional way in which Windows and Linux treat I/O is far from ideal. In this article we will look at these traditional ways, and show a new way which makes Linux a clear winner over Windows for hardware I/O.
Who cares about hardware I/O, where a computer directly controls hardware?
Hobbyists love hardware I/O to control microprocessors, motors, robots, their home and more. For example in the February 2004 Linux Gazette, Pramode C.E. interfaced Linux to a PIC microprocessor (see "Let's Build a Cool Linux Toy").
Engineering students who do subjects in electronics and computing often need to interface their computer to hardware. This can make great projects and to be used to test constructed hardware.
Industry needs cheap and powerful controllers for
manufacturing and serious hardware control. In many cases Linux
works just fine, especially with some of the real time extensions
that can be added. To read about real time extensions see the GPL
RTAI at https://www.aero.polimi.it/~rtai
or the free RTLinux from FSM Labs at www.fsmlabs.com
At RMIT University where I work we have a security system based
on Redhat 6.1 that has been operational for some 8 years. It has
briefly crashed twice, once due to the log file filling the hard
disk and once due to a problem with the database engine.
DOS, Windows 95 and Windows 98 did not block I/O access in any way so command line or GUI programs could happily control I/O. This made I/O access easy but created both a vulnerability and a security hole. A misbehaving application could accidentally access any I/O address and so create havoc with network cards or hard disks. A malicious application could deliberately access networks or disks, and possibly wipe the entire hard disk.
Even if these problems are acceptable to you there is worse - none of these operating systems are supported by Microsoft and most of the newer applications and development tools do not run on them. Few people want to run such old systems so anything you create most probably wont be used by anyone else.
Windows 2000, NT, ME and XP all block I/O access by using
security features build into the x86 microprocessor core. You would think
this meant no I/O access is possible but - yes, there is a hack. For
example, two programs called giveio.sys
and
totalio.sys
can be used to bypass I/O security; you can
download giveio.sys
to suit Windows XP from https://www.physik.rwth-aachen.de/group/IIIphys/CMS/tracker/en/silicon/arcs_nt.html.
Such programs have several problems. Firstly, most of them give I/O access to every port, or access to a limited range of I/O for every application. Perhaps more important is the fact that these programs are hacks, and the very next Microsoft security update may render them useless. In fact, the existence of these I/O programs represents a security weakness for Windows so Microsoft should be finding ways to block their action.
Traditional Linux I/O restricts I/O access to the root user, or to device drivers which run in kernel space. For applications running under root privilege, the C code function ioperm() can give access a limited range of I/O ports between 0 and 0x3FF. The C code function iopl() can give access to all I/O ports (0 to 0xFFFF) and allow the application to turn interrupts on and off. See the UNIX info pages on both these functions for more details.
On close inspection none of these methods are really acceptable. It is very foolish to work as root during software development as a simple mistake can delete key parts of the operating system and so make your system crash and burn. Running an application with root privileges opens up all the vulnerability and security problems found with DOS and Windows 98. Writing kernel drivers is no better as they have the same security weaknesses and are significantly more difficult to write and debug compared to normal applications.
On the face of it, it seems as if Linux is little better than Windows for hardware I/O. It has the same weaknesses but at least I/O access will not disappear with the next security update.
Linux allows significant manipulation of the permissions and ownership of running software processes. It is possible to link together several well defined Linux features to create a bullet-proof method that will allow a user-level program to have I/O access to a limited range of I/O, typically just a parallel printer port or serial port. This means no security weaknesses and very easy programming of hardware I/O.
The tricks used to make this happen are as follows -
su - root # become root, give password when requested. chown root IO_enabler # make root the owner of the enabling program. chmod u+s IO_enabler # set the SUID bit.
The I/O enabling program is run and first enables I/O access but only to the desired ports - typically all parallel printer ports and the serial ports. This is done using the C function ioperm().
Optionally the process priority can be set using the C function setpriority(). A higher priority will ensure the application about to be started experiences fewer delays as other process run at a lower priority.
The I/O enabling program then drops down to the privileges of the real user who started it using the C functions setgid() and setuid(). These functions do not effect the I/O access permissions or process priority.
The I/O enabling program then loads the application program over its own code space by using the C function execvp(). This means the application program is now running with user (not root) privileges, and with I/O access limited to only those I/O ports enabled by the trusted I/O enabling program.
Running an application becomes quite simple -
./IO_enabler ./application_program parameter_1 parameter_2 ......
The code behind the I/O enabling program is surprisingly short -
/*--- optionally set process priority, 0= normal, -20 highest, +20 lowest.*/ setpriority( PRIO_PROCESS, -10, 0) ; /*--- get access to the ports.*/ if (ioperm(0x378, 3, 1)) perror("Failed ioperm lp0 on") ; /*--- remove root privileges.*/ setgid( getgid() ) ; setuid( getuid() ) ; /*--- overwrite code space with the application program, send arguments to the application program.*/ execvp(argv[1], &argv[1]) ; /*--- if get here exec must have failed.*/ perror(" execv failed") ;
[ Note: in general, creating SUID programs without understanding all the security implications of doing so can be very dangerous. Do try this at home... but read much and consider deeply before you do. -- Ben ]
Here are a few real bits of code to get started. Unless otherwise stated, compile a program ( lets call it 'x') as follows -
gcc -o x x.c
lp_tty_start.c: this I/O enabler allows access to the standard three printer ports and four serial ports but nothing else. Ensure it is owned by root with the UID bit set as described earlier in this article.
port_write_then_read.c: this will write a single byte to an I/O port and read back the new value of the I/O port. It is probably best to use /dev/lp0 data port (888) to test this program. To ensure that your /dev/lp0 is active, reboot your machine and go into the BIOS setup. Ensure the parallel port is in either "compatible", "SPP", or "bidirectional" mode. Remember to save your BIOS settings on exiting the setup screens. Start the program as follows to write 85 (55 hex or 01010101 binary) to the /dev/lp0 data port which will come out on pins 2 to 9 -
./lp_tty_start ./port_read_then_write 888 85
iopl_start.c is another I/O enabler based around the iopl() function. This gives access to all I/O ports (0 to 0xFFFF) and has the ability to enable and disable interrupts. Like lp_tty_start it must be owned by root and have the SUID bit set.
blockio.c must be started with iopl_start. It will turn off interrupts and hang your machine for about 8 seconds, as well as causing pin D0 of /dev/lp0 to output a 500 kHz squarewave. After 8 seconds the program will terminate normally and your machine will resume its usual operations.
Start this as follows -
./iopl_start ./blockio
hide_lp_tty_start is a script file that shows how to hide the fact that your program was started using lp_tty_start. Unfortunately programs started thus way must take a fixed set of parameters. The application is simply a read of the data port of lp0 using port_read.c. Start it as follows -
./hide_lp_tty_start
What ports have you got? The code below implements a program that will check which serial and parallel ports are active and available on your machine. To make the program easy to run, the code is designed to be executed as root (not really a security issue, as you have the source and can see the code is benign).
main.c shows the basic flow of the code, and the instructions for use.
pp_access.h and pp_access.c form a neatly packaged parallel port access module that can save and restore the parallel port state, and help manipulate the bits in the port. You may want to reuse this module in your own applications.
serial_access.h and serial_access.c provide a similar functionality for the serial port.
make_pp_serial_check is a script file that compiles the code above to make an executable. Compile the code using make_pp_serial_check and execute the program (./pp_serial_check) as root with no parameters to see the instructions. The program can identify whether ports exist and perform a simple loopback test as further verification that the port works.
Remaining dangers: the software dangers of using I/O have been largely eliminated by using the I/O enabling programs shown in this article but one danger does remain - if you fail to design the hardware circuitry properly, or mis-wire it, then you can blow up your precious parallel port (serial ports carry only signals, and are therefore much harder to damage.) Fortunately, there are a few simple warnings and a few simple circuits that can protect your ports. If there is enough response to this article then perhaps we can follow up with an article on safe hardware interfacing.
Where to now? This article has shown how user-level programs can safely access I/O in a very simple manner. Linux should now be the operating system of choice for all hardware I/O. It should be the operating system of choice for engineering students, computer science students, hobbyists and industry that uses hardware I/O.
Linux can access not only the standard ports such as the printer port; it can also connect to many of the plug-in cards that provide more sophisticated functions, such as relay cards and high speed analogue I/O.
What is needed is a wider range of I/O projects visible on the web so students, hobbyists, and practicing engineers have a head start when they create projects. New projects are needed and a lot of those old DOS projects need to be updated and ported to Linux. Can you be part of the Open Source community and help?
PJ Radcliffe is a senior lecturer at RMIT University in Melbourne
Australia. His career started as an electronics/microprocessor engineer at
Ericsson followed by consulting work in hardware and software, then an
academic position at RMIT. Teaching has become a great pleasure, especially
when linked with technologies and issues relevant to the workplace. In 2004
he received an award for "Student Centred Learning" from RMIT.
For many years he was a Microsoft junkie - but then had to run a lecture
series on Linux, and got hooked. Who wouldn't be? Linux can be used as a
turn-key GUI like Windows, a powerful server, and to control hardware.
His interests apart from Linux, software and hardware are... ( I'll
remember in a tick)... (context switch)... a lovely wife who hates
computers (the other women in my life, you see), three really nice kids,
and a rather large garden.