Running a GDI printer under Linux
part 3 - Tools and Techniques


There are some other articles on this subject as well as the motivations for this work in my homepage. This is the third article on the series Fighting against GDI. As good soldiers, all Linuxers are invited to battle the enemy, writing their winprinters drivers in the behalf of the free software world. Please forgive me for the bad style and language, for I don't have a good english teacher available to revise my writings. Feel free to send me corrections.


About two weeks ago I went to a store to buy our monthly food and supplies and  I found there a real bargain: a laser printer for under R$ 600, (brazilian "reais". One real is changed for about 0.52 US dollars.) from Samsung, the ML-85G.  Usually I walk around from GDI printers, because of the trouble to get them working.  But I thought the time arrived when Linux is proliferating around us and I have several customers that want to enter the new wave. I decided then to enter the game, and as a good gambler I was not sure I was going to win, but I bet to try.

In the first time, I have connected the printer to an old 486 with Win95 installed and connected to my home network to see how useful were it going to be.  With Samba, ghostview, including gprint, it worked well, but I noticed then that most of times Win95 were calculating the pixels and, as the memory inside the printer was only 512K, there must be some kind of very fast protocol to send the data as the printer drummer was being burned by the laser beam. Of course, I have had some previous exposure to lasers and electronics technology, as I make my life designing parts and assemblies for the industry.

Then I looked for the various alternatives available.  I did already know that disassembling from windows programs is a burden at least, as one should expect to find many instruction modifying code, calls followed by extra inline arguments and all kind of masquerading to turn it in a difficult enterprise. But what should we expect from an interface with a limited number of pins? We have only 8 data bits, 5 input status signals and 4 more controls signals. So, it should not be so near to impossible as one could expect.

Two alternative schemas caught my attention: (1) simulate a complete hardware environment, so I can trace all accesses to those parallel port pins and get a sequence of events; (2) capture data in real time, using another gadget, or even another faster PC to accumulate the results, while the printing occurs at the normal speed.
I have tried both approaches with the most appropriate tools I could find and this article shows some of the reasoning and critical issues involved in the acquisition process.  The guiding point of my process is capturing a large data collection and then analysing it with the best intelligent tool invented so far: the human brain!  Nobody could possibly create something that complex that couldn't be simulated if we know everty detail of the interface and protocols. So don't expect me to explain everything, just patterns of data that will be simulated by another software, the Linux device driver.  I actually found that some data with similar properties could be grouped in some meaningful subroutines (that's exactly what subroutines exists: to group similar procedures) and then some revealing detail appear with the time from our bindly gathered data.

Some protocol issues

The parallel port protocol came from Centronics, as we know it.  It has very simple handshake signals to tell the printer when the data is ready to be read, and to tell back the computer that the printer actually processed the character, before moving to the next character.

Almost every parallel port printer follows this de facto standard, also known as IEEE 1284 (IEEE is the Institute of Electrical and Electronics Engineers, an association which I'm proud to participate as a member). The basic signals are the STB, BUSY and ACK signals, as shown in the figure.  GDI printers don't honor these signals. In fact, I found that mine laser printer transfer most of its data without any handshake signal being generated or tested.

What to look for then? Think! Use your intuition, or ask some friend if you are clueless. You can also send some captured data to me, but please, try first to find out by yourself its meaning, for I'm not a magic nor I have all the time of the world to spare. If you find something really difficult to find with your tools, so please send to me. Probably I will find it interesting too and can help you to conduct more experiments and solve the maze. The central idea is to look for discrepant values embedded in a otherwise boring and repetitive pattern. Yes! The enemy uses such camouflage of signals to address you to an unteresting signal or pattern so you will find lost even before reaching the first real signals.  Please look at this captured data from my first experiment with RT-Linux and the circuit I will show you later:
 

80 a0 00 a0 89 8a a6 07 a7 8b 8b 89 8c 8c 04 94
3f 95 58 94 95 89 8a a6 07 a7 8b 8b 9a 89 8a a6
07 a7 8b 89 8a a6 07 a7 8d 46 89 8a a6 07 a7 89
8a a6 07 a7 8e 89 8a a6 07 a7 8d 4f 89 8a a6 07
a7 89 8a a6 07 a7 8e 89 8a a6 07 a7 8d 01 89 8a
a6 07 a7 8e 97 00
This is very boring and don't show up what this is really. It was captured from the data lines (D0~D7) of the printer port from a second machine, qualified with the STB (strobe) signal. This means that, when STB goes active, it store one value (8 bits), then waited till STB is inactive, and repeat the process. However, this data can be more meagninful if we rewrite it like:
80 a0 00 a0
89 8a a6 07 a7 8b 8b
89 8c 8c 04 94 3f 95 58 94 95
89 8a a6 07 a7 8b 8b 9a
89 8a a6 07 a7 8b
89 8a a6 07 a7 8d 46
89 8a a6 07 a7
89 8a a6 07 a7 8e
89 8a a6 07 a7 8d 4f
89 8a a6 07 a7
89 8a a6 07 a7 8e
89 8a a6 07 a7 8d 01
89 8a a6 07 a7 8e 97 00
In the end, it turned out that this data is not really data but a giant stream of commands given to the printer ASIC, just as the camouflage of a small number of required signals. As I said before, the data is being sent with its own embedded handshake (the most significant bits). To read the enterlines fo the data remember the old psychological tests you're presented to enter high school or the puzzles your parents bought to you. It is like a joke, and if you're intelligent (aren't you?) you are going to win.  You don't need any fancy programs, just a couple of sed scripts. Personally, I like tcl, because it is my one-size-fits-all tool for many jobs. If I need a GUI (graphical user interface) it's there with tk. If I some interface to many others libraries, it's already implement or almost ready. Or if I need to really custom some new featuree, it is easily interface with C, even easier than many toolkits available for unices.
 

Bochs and boxes

Kevin Lawton gave me a great gift by writing this nice and well documented program. Mandrakesoft decided to make it GPLed and sponsored Lawton's company, so we can have the best tool to spy inside deeply unknown programs, like the GDI layer of MS-Windows.  Altough Bochs have itself some nice and documented instrumentation interface, I'm somewhat undisciplined, so I made several patches on its sources to get  quick-and-dirty tricks of capturing and passing to the real hardware input/output accesses at the parallel port.
Such accesses must be given from a root (superuser) account and be allowed by ioperm() or iopl() system calls. Please refer to their manpages to know how to use them. They are a quick way for writing the final device driver outside the Linux kernel too. When I have some time to cleanup the dirty I've left...

Bochs is a PC hardware simulator, very flexible, that also simulate the peripheral hardware like video card, mouse, keyboard  and disks of a virtual machine. The cpu can be chosen from a 386, 486 or Pentium class machine, with even the instructions per second speed selectable.  But all of this is done by C programs, so the speed is significantly lower, though useful for many experiments. Most of my GDI printer spying was done inside this virtual machine. You need to prepare a disk from scratch inside a linux file to get it running. Look that I said "a disk from scratch" and not a partition.  You can use fdisk with the filename, after reading the documentation and choosing a suitable disk geometry, than creating your msdos partition and formatting it. Then you can copy the files needed with the mtools package programs. I prefer instead to mount the partition with something like:

mount -t msdos -o loop,offset=$[ 63*512 ] /opt/252M /bdsk
Notice the expression $[ 63*512 ]  in shell syntax. The 512 is the size of each disk sector and the 63 is the first sector of my msdos partition, as reported by fdisk. If you're a systems administrator you're in your own, otherwise, RTFM.
Unfortunatelly, Bochs (that sounds "box", referring to the linuxes and bsd boxes) don't have parallel port support, not even in its BIOS (basic input output system).  As I plan to make all parallel ports access actually occur, I just started the main() with an ioperm(0x378,3,1). Please get the patched version of Bochs. For your convenience I included a pre-compiled binary, but the better is that you compile it yourself, as you will need to change the basic capture conditions several times with the feedback from previous runs. You must be aware too that Bochs need the disk before ready to start. You can make it boot from a floppy disk, but in the long run it's better if you install the target operating system inside the simulated disk image.  You may find difficult to install it from scratch, in each case you should install it very plain (vga, ms mouse, standard keyboard, etc) in another machine and the copy the pre-installed version to your image disk. Mount it like shown above. And please, be patient as Bochs is very very slow, as it simulate all instructions. Anyway it is fairly accurate and that's mos importatan to our experiments than speed. You will have plenty of time to run while you think on the results from the previous runs.

The real capture is written inside two routines under iodev/devices.cc in the Bochs source directory. The routines are bx_devices_c::inp() and and bx_devices_c::outp().  We only need them to see if our i/o range is being selected and then write the data to one pre-opened log file.  We have to write very compacted things to not spend much time inside our code. It's far better to leave the interpretation to the other offline programs.  Here is a sample of my captured data:

O2,2 i1(f)
I1(f)
I2(c2)
O2,0 i1(4f)
I1(4f)
I1(4f)
I2(c0)
O2,2 i1(f)
I1(f)
I2(c2)
O2,0 i1(4f)
I1(4f)
I1(4f)
I2(c0)
O2,2 i1(f)
I1(f)
Here we have "O" meaning output, "I" input. The number given next to it is the offset from the base port (0x378 for the first lpt) and then the data value. A "i" output means that we have read the port without any request from the intervenient operating system. I do this so we can know what was read fro the status port, just after MS-Windows write to the data or control port (where Windows didn't request it). Then I compare the previously read value with the same data requested later. Remeber that the speed of "virtualization" is hundred of times slower than a real machine. It is not time dependant for Windows, because Bochs fools it to think that only a short time elapsed, but the real devices (like the printer) have much faster responses.
Notice the data given in the begining of this article was extracted from large pieces of this kind of data streams. It was filtered with grep for only the "O0," lines, edited to strip all "O0," and "i1(.*)", and then grouped starting with the first character of the repeating pattern by a small tcl program:
 
#!/usr/local/bin/tclsh

set s ""
set n 0
while {![eof stdin]} {
    gets stdin line
    catch {set byte [expr 0x$line]}
    if {$byte == 0x89} {
        puts $s
        set s ""
        set n 0
    }
    if {[incr n] == 16} {
        set n 0
        puts $s
        set s ""
    }
    append s "[format %02x $byte] "
}
puts $s
Of course, you can easily construct your own tools with your favorite language. Use your creativity!

Sometimes there is some hardware time-critical isues found that prevent us from getting the data with a working printer. In that case, Bochs can't give us much. We could look for other simulator, but I will show you some alternative ways (with RT-Linux) for gathering the data with the printer running at full speed. You will need to fire your soldering iron or grab your wire-wrapping tool for assembling a small 4 parts circuit. For now get a couple of  printer cables (yes, I said a couple) and get some 74HC374 integrated circuits and a printed circuit board or some experimenter's perfurated board.
There is no way the devil can hidden itself. The believers will ever win!
Wait for the next article and send me your comments. The saga will continue.



Rildo Pragana <rildo@pragana.net>     Adventures in Linux Programming