USB Parallel Port adapter, low-level coding usblp.c

A project log for Potentially Useful/Obscure Linux Stuff

Things that I've found useful/hard-to-find in my Linux endeavors...

eric-hertzEric Hertz 08/06/2016 at 21:3916 Comments

Update Jan. 2019: Apologies, this was written with #sdramThingZero - 133MS/s 32-bit Logic Analyzer in-mind, and therefore is *way* overcomplicated for most projects. If you just want to write some outputs (blink LEDs, control a text-LCD, etc.) please check out the link in the comments. You could probably control a 7-segment LED with a little wiring and "echo x > /dev/usb/lp0". Input, however, may be a little more difficult, I don't know.

However, brief note as to the main point, here... the parallel port may've been scanned by linux for attached-devices, which may leave your parallel port in an unknown mode.

This is about checking and changing the parallel port mode. 

(Similar to setting the baud-rate of a serial port.)


So, you've got a USB-to-Parallel adapter that you'd like to use for a project (not a printer)...

(Hey, I'm no expert, here... so take this as the ramblings of a newb):

First: There are Major Limitations... These adapters are *nothing* like the ol' Parallel Port built into old motherboards or on ISA cards... So, don't be getting high-hopes about bit-banging this thing. (There's lots of info about this all 'round the interwebs).

BUT: If your project *can* work within those limitations, then you'll first need to figure out how to set the adapter into the right mode...

So, what're those limitations?

I'm no expert, here, but it seems there are three "modes" these things can work in...

  1. Unidirectional
  2. EPP/ECP compatible
  3. IEEE-1284 compatible

1: Unidirectional -- Might work for your project... If *timing* isn't particularly-sensitive, you should have access to the 8-data-bits as outputs, and 3 control lines as inputs (if I understand correctly)... It could probably be done-ish... but nothing like the level of functionality the ol' ISA cards had.

3. IEEE-1284 -- This guy looks pretty gnarly and basically requires a microcontroller on the "peripheral"-side to handle the protocol. There ain't much to bit-bang with this method, unless, maybe, you're a truly-1337 haxor.

2. EPP/ECP -- This is the one I'm looking at.

So this guy doesn't look *so* complicated. And, in fact, it looks like it might be darn-near exactly the sort of interface I need for #sdramThingZero - 133MS/s 32-bit Logic Analyzer, and really not that much different from, say, the interface on an HD44780 Character-LCD.

The basic idea is to use the Data I/Os bidirectionally. This gets a bit more complicated with the whole USB thing, since you've got to send a request for a certain number of *reads* or *writes*... which means, for *reads*, your peripheral has to be able to respond with acknowledgements... (I think I can get around that with an OR gate, in my case)... and has to set-up the next byte to be transmitted, etc.

Anyways, sidetracked. The point is, when you plug in the USB-parallel adapter, it doesn't look to the computer like merely a parallel port (like a USB-serial adapter looks like nothing more than a serial port)... instead, Linux tries to look for a *printer* connected to that parallel port.

In a high-level sense, it makes sense... it's just another bus; parallel, USB, PCI, SCSI, whatever...

But that means it's got a procedure for looking for attached devices, and when it can't find one, it might just leave your new "parallel port" in a mode you don't want...

So, there're some settings you might want to send your new "parallel port"... but how do you do that when all you've got is /dev/usb/lp0, fopen(), fclose(), fread(), and fwrite()...? Yeah, no... ioctl() is the way to go.

That's, basically, the same thing used for e.g. setting a serial port's baud-rate... you can't do that by just sending some ASCII characters to /dev/ttyUSB0, you have to use ioctl(). 

(or use a utility like stty or setserial, which calls ioctl() as-appropriate. I have yet to find a similar utility for parallel-ports.)

OK, then... But this USB-adapter needs a mode-setting, and with my search-fu there's basically *Zero* documentation on the IOCTL arguments available to USB dongles.

SO, you've gotta look into <Kernel Source>/drivers/usb/class/usblp.c and, sure-nough, there are some IOCTLs listed. Awesome.

Then you'll discover that there's no Header File for usblp... And, again, it seems no one actually uses these IOCTLs, so how do *We* use them?

Yeahp, you gotta copy a bunch of #define's from usblp.c to your own code (rather than #including some standard-ish system header-file). Don't believe me? Take a look at which is the *only* thing I could find through all of the google (and actually didn't find it through there) that used these IOCTLs... And, lo-and-behold, that's exactly what they did (copied those #defines into their code).

So, it's a bit of work, but it does appear to work...

Here's the goods... (and the bads...)

//Pondering using a USB-to-Parallel adaptor 
//They seem to have three modes:
// One-directional
// IEEE-1284

// One-directional likely won't work
//  (Would with an *internal* and bit-banging)
// IEEE-1284 looks a bit too complicated for my needs
// EPP/ECP looks to be the way to go...
//  Bidirectional, using the same 8-bit data lines
//  Strobe, etc...

//First we need to make sure it's *possible* to set it in EPP/ECP mode!
#include <stdio.h>
#include <sys/ioctl.h>
//#include <linux/drivers/usb/class/usblp.h>

//ALL THESE for open()?!
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <errno.h>
#include <string.h> //strerror

#if 0
 #error "YEP"

int onError(char *location)
      printf("%s: Error %d, '%s'\n", location, errno, strerror(errno));
      return 1;
      return 0;

int printerPort_FD;

//These are defined in drivers/usb/class/usblp.c
// But there doesn't appear to be an associated header-file!
//This similar manual-entry (rather'n #including) is done in hpoj-0.91
#define IOCNR_GET_DEVICE_ID      1
#define IOCNR_GET_PROTOCOLS      2
#define IOCNR_HP_SET_CHANNEL     4
#define IOCNR_GET_VID_PID     6
#define IOCNR_SOFT_RESET      7
   /* Get device_id string: */

/* The following ioctls were added for */
/* Get two-int array:
  * [0]=current protocol (1=7/1/1, 2=7/1/2, 3=7/1/3),
  * [1]=supported protocol mask (mask&(1<<n)!=0 means 7/1/n supported): */


/* Set protocol (arg: 1=7/1/1, 2=7/1/2, 3=7/1/3): */

void getProtos(void)
   //Huh, isn't this architecture-dependent...?
   int twoInts[2];

   //Bug in hpoj...?
   //int twoints[2];
   //      &twoints) <---- twoints is already an address... &(twoints[0])!
   ioctl(printerPort_FD, LPIOC_GET_PROTOCOLS(sizeof(int[2])), 


   printf("Current Protocol: %d\n"
          "Supported Protocols (Mask): 0x%x\n", twoInts[0], twoInts[1]);

int main(int argc, char* argv[])
   //ioctl requires a file-descriptor not a FILE*...
   // And we might want some of those other O_ options, as well.
   //FILE *printerPort;
   //printerPort = fopen("/dev/usb/lp0", "r+");

   printerPort_FD =
      open("/dev/usb/lp0", O_RDWR); //Maybe O_NONBLOCK?
   if (printerPort_FD == -1)
      if( onError("Open") )
         return 1;
      //printf("Open: Error %d, '%s'\n", errno, strerror(errno));
      //return 1;

#define STRING_LEN  1000

   char ioctl_return[STRING_LEN] = { [0 ... (STRING_LEN-1)] = '\0'};
   ioctl(printerPort_FD, LPIOC_GET_DEVICE_ID(STRING_LEN), 


   printf("DEVICE_ID: '%s'\n", ioctl_return);


   if(argc > 1)
      int newProto = atoi(argv[1]);
      printf("Per Request: Setting Protocol to %d\n", newProto); 

      //man ioctl is a bit confusing... or this is implemented weird
      // don't send a pointer to newProto, send newProto itself.
      ioctl(printerPort_FD, LPIOC_SET_PROTOCOL, newProto);




note that it has to be run 'sudo' on my system, despite having added myself to the 'lp' group... huh.

So, now you can set your usb-parallel dongle's port to whatever mode you like. SPP should be the easiest, allowing you to e.g. 

"echo x > /dev/usb/lp0" 

and see the binary value of 'x' displayed on those output-pins.


Ken Yap wrote 01/12/2019 at 14:53 point

>note that it has to be run 'sudo' on my system, despite having added myself to the 'lp' group... huh.

The reason is simple. The Linux kernel doesn't know anything about users or groups, only root and non-root (uid 0 and not). User and group names are userland stuff. When you have a file which has user and group owners then permissions can be enforced. But calling ioctls doesn't involve filesystem objects so the kernel can only say "these system calls are only allowed to root". Traditinally the solution is to write a setuid root program that enforces its own restrictions.

  Are you sure? yes | no

Eric Hertz wrote 01/13/2019 at 06:33 point

Interesting! I had no idea users'n'groups wasn't kernel-level.  

I take it this is a different problem than the reason I need to sudo for something like using avr-dude with a usb-tiny-isp.

I'll have to look into this technique (setuid program)!

  Are you sure? yes | no

Ken Yap wrote 01/13/2019 at 08:18 point

If it's one of not being able to write to the USB port, this is usually solved by creating a hotplug udev rule to change the group to one you are a member of when the device is plugged in. For example when I plug my Arduino in, the group is set dialout (on my distro) and the group permissions rw. A search will find you tutes on this.

  Are you sure? yes | no

Eric Hertz wrote 01/14/2019 at 01:15 point

yeah, that's it.... a udev rule. 

What makes the other situation different (requiring setuid)? Do the udev-situations only require r/w access to /dev/whatever (if such even gets created for such devices whose 'driver' is essentially purely in userspace, no? I figured they'd need lower-level usb-transaction/register access than /dev/whatever would provide. nevermind, I think, that some of these devices (e.g. ft-2232h) have so many different and/or unusual configurations that treating it as a character/block-device seems a bit weird) as opposed to needing ioctls?

  Are you sure? yes | no

Ken Yap wrote 01/14/2019 at 01:38 point

udev rules act on devices so after the rule has changed the owner or group and/or permissions of the device, the kernel can check that your uid or gid(s) has access to the file which has owner and group ids. Note that no user or group names are involved in the kernel, the check is numeric.

  Are you sure? yes | no

Ken Yap wrote 01/11/2019 at 08:29 point

If you just want GPIO, try a CH341 USB dongle: The Linux ch341 driver is in-tree already. I have one that uses just the UART feature and it worked right off.

  Are you sure? yes | no

Eric Hertz wrote 01/13/2019 at 06:16 point

Cool. great page, though I have to admit I hesitated to click.

This seems a reasonable option! A bit like the FTDI ft-2232h (two twos), it would seem.

  Are you sure? yes | no

Eric Hertz wrote 01/11/2019 at 07:45 point

Ooooh! Maybe it's as simple as something like: "echo PCSPP > /proc/devices/parport0/mode"

  Are you sure? yes | no

Eric Hertz wrote 01/11/2019 at 05:59 point

random poorly-thought-out braindump:

@Starhawk has been trying to get an OLD inkjet working without success, bringing me back to this topic.

Surely I've looked into this before. But here's a list of parport ioctls:

(NOT USB->parallel)

Surely, some of those are very similar, including mode-setting, etc. But, as I recall, the pl-2305 driver doesn't support all the parport ioctls, so *may* or may-not work with wares designed for parallel ports. 

(Depending on whether they expect to use usb-parallel-unsupported ioctls, AND whether the similarly-defined ioctls are enterable in the same way. I think those definitions are merely constant integers, so e.g. do the values IEEE1284_GETMODE == LPIOC_GETMODE, and use the same arguments, etc.?)

Then, it may be plausible to use *some* warez designed for /dev/parportN with /dev/usb/lpN...

Then, in the case of an old inkjet which likely uses SPP and doesn't identify itself, it *may* or *may not* work with a sophisticated program like cups expecting direct parport ioctls and attempting to auto-negotiate the parallel-mode. But, surely, that autonegotiation accounts for such early-style devices (e.g. dot-matrix line printers) which don't identify themselves.... so, then, the below sorta technique may actually work with things other than hplip (which is the only thing I've found that attempts to use pl-2305's ioctls), e.g. the bj-100 driver which most-likely is completely unaware of usb-adaptors. AS LONG AS the driver doesn't make use of the unsupported ioctls (are there really any parport ioctls that couldn't be implemented, even as simply as 'return false,' for the usb drivers? And how is it they didn't already?!):

"DeviceURI parallel:/dev/usb/lp1"

or maybe: would put it in spp.

or /proc/ ?

  Are you sure? yes | no

Starhawk wrote 01/11/2019 at 06:04 point

Oh hi... [nervous chuckle] Is Miss Otis in...?

(reference -- )

  Are you sure? yes | no

Starhawk wrote 01/11/2019 at 06:05 point

(...I really should go to bed, it's five past 1am here!)

  Are you sure? yes | no

Eric Hertz wrote 01/15/2019 at 06:14 point

Ha! Well, Miss Otis regrets she's unable to lunch today. (apologies if calling yah out, this post was a reminder to myself, next I revisit the topic)

  Are you sure? yes | no

Leandro R Lopez wrote 08/26/2017 at 20:26 point

very very interesting
I ran tests with a usb printer adapter and it worked.
Is it possible to write binary data in it? As a pin of the 8 output?

  Are you sure? yes | no

Mitchell wrote 09/13/2017 at 05:42 point

To write to individual pins, see this post:

Basically you just write the binary or hex output you desire. e.g for all outputs high, 0xff or 0b11111111.

  Are you sure? yes | no

Eric Hertz wrote 01/11/2019 at 07:24 point

This is great!

 It demonstrates a few *really* handy things:

First, tying some signals to specific and unchanging levels. E.G. "BUSY" is wired such that the OS is told the device is *not* busy.

Second, bit of loop-back. E.g. tying Strobe to ACK (is that right?) so the device itself doesn't have to do the work of actually responding. The request *is* the response.

Third, using some otherwise unused signals to supply power to the device.

Put those all together, and "the device" doesn't have to have any 'smarts'. It could simply be a handful of LEDs, ala the old-school 'real' parallel-port days.

Except for one thing:

Note how the author has added a latch to each output.

If I understand correctly, this is necessary because the author's usb-parallel adaptor is in ECP/EPP mode by default. 

(I don't know whether this is always the case, e.g. if the author's computer had a driver/module probing, on boot, for an IEEEE-1284 scanner, the parallel port might be left in IEEE-1284 mode, instead, and his circuit may not work).

SINCE his appears to default to ECP/EPP, the latch, then, is necessary because the data-bits are bidirectional, so don't hold their value.

Great Solution.

Also, paves the way for more complicated circuitry (e.g. *input* as well).


However, if *all* one wants is output (and plausibly a couple inputs?), SPP/Unidirectional mode should remove the need for that extra latching circuitry.

So, I guess, with a little utility to throw one's parallel-port into that mode (not much different than using stty to set a serial port's baud rate, and kinda laid-out, here), one could drive their LEDs with no additional circuitry and no need for coding. "echo x > /dev/usb/lp0"

  Are you sure? yes | no

Eric Hertz wrote 01/15/2019 at 06:33 point

Apologies for the long-overdue reply, I guess I misread this several times.

You actually tried-out the switching of modes? 

If you set it to SPP/Unidirectional, you should now be able to simply:

echo x > /dev/usb/lp0

or, in C/whatever, open the /dev/usb/lp0 'file' as *write*, and fprintf, etc.

I do not, currently, have access to one of these, so can't test. You *may* have to tie a couple pins together so linux doesn't think the printer is offline. The link Mitchell gave talks about that. Though, I think most of that link is for EPP/ECP-mode. For SPP mode, I'm pretty sure ack is unnecessary, maybe some others. 

At this point, I believe, it should work just like a normal SPP parallel-port, accessed at the /dev/ level (as opposed to e.g. using direct port-writes via outp()).

Hope that helps... 

I've, unfortunately, kinda turned this page into a catch-all brain-dump that maybe will get revised/simplified for tutorial-ish form again, later.

Feel free to ask questions! Here's to faster responses...

  Are you sure? yes | no