hpoj reference: ptal-mlcd
The ptal-mlcd daemon implements the HP MLC (Multiple Logical
Channels) and IEEE 1284.4
packetized transport protocols over parallel ports and USB, and
enables access to the various MLC/1284.4 services on the device,
such as print, scan, and PML. Applications use the PTAL frontend API
to access local or remote devices, and libptal accesses
locally-connected devices controlled by ptal-mlcd through
the use of Unix-domain sockets, which, unlike TCP/IP sockets, are not
accessible across a network.
Currently, ptal-mlcd performs parallel-port I/O by accessing the
hardware registers and performing the IEEE 1284 ECP- and nibble-mode
signalling directly from user mode, with no kernel assistance. In most cases
it now makes use of bounded ECP mode and hardware assistance for faster ECP
transfers, by writing/reading bytes to/from the ECP FIFO registers. However,
it doesn't make use of hardware interrupts or DMA, given that it runs in
On the other hand, ptal-mlcd's USB support depends on
libusb and/or the
Linux kernel USB printer-class driver (printer.o).
libusb is required
in order to access all device functions on certain
"composite USB" models
and to work around SMP issues in printer.o, but otherwise the two
methods should work equally well. See the hpoj
Supported Devices page
for more information about requirements for different models.
Click here for more information on
setting up basic device connectivity
with the hpoj software.
The ptal-mlcd command-line syntax is as follows:
ptal-mlcd [mlc:]bus:name [options...]
- bus is the connection type and is one of:
- par -- Parallel port
- usb -- Universal Serial Bus
- name is the desired name or number suffix for this device
- Common options for all bus types:
- -devidmatch string -- Matches portion of device ID string,
such as the model or serial-number fields to uniquely identify a device
- -remconsole -- Enables remote debug console (service name
PTAL-MLCD-CONSOLE, socket -1), also makes log message output less
susceptible to data loss but slightly more susceptible to deadlocks
- Less common options for all bus types:
- -nofork -- Stays in the foreground, enables local console
- -nodrain -- Disables channel-78 reset and reverse data drain
in case they cause a problem
- -forcemlcdot4 -- Forces both 1284.4 and MLC modes to be tried
- -nodot4 or -nomlc -- Disables 1284.4 or MLC mode
individually (Caution: Don't disable both, because "raw" mode currently
does not work very well.)
- -nopml -- Disables PML multiplexing
- -nolog, -logwarn, -log -- Sets logging level
- Valid options for par:
- -base 0xADDR -- I/O port base address (default is
- -basehigh 0xADDR -- ECP high base address
(default is base+0x400, must be specified after
- -device /dev/lp0 -- Path and filename of corresponding
device node, used to lock the port and prevent interference from other
- -porttype porttype -- Overrides port-type detection,
- spp -- Standard (unidirectional) parallel port
- bpp -- Bidirectional (PS/2, not ECP) parallel port
- ecp -- Extended Capabilities Port with no hardware assistance
- ecphw -- Extended Capabilities Port with hardware assistance
- ecphwfwd -- Extended Capabilities Port with hardware assistance
in the forward direction only
- ecphwrev -- Extended Capabilities Port with hardware assistance
in the reverse direction only
- -nobecp -- Prevents bounded ECP mode from being used
- -forcebecp -- Forces bounded ECP mode to be used
- Valid options for usb:
- -device "/dev/usb/lp*"... -- Path and filename of Linux
printer.o device node(s) (it's OK to have multiple device nodes
and/or wildcards, preferably not expanded by the shell)
- -device %bus%device --
libusb bus and device
- -vpidmatch 0xVVVVPPPP -- Matches USB vendor and product ID
instead of -devidmatch
- -nocomp -- Disables composite-USB support (FF/D4/00 or FF/FF/FF
- -nocompprint -- Disables composite-USB "raw" print interface
(7/1/2) and always tries to print over (possibly composite) MLC/1284.4
- -forcecomp -- Enables extra composite-USB support (FF/FF/FF
MLC/1284.4 interface) that would not normally be used in most cases
- -noglobusb -- Disables implicit libusb device globbing
- -nochannelchange -- Disables HP USB "channel change" request
ptal-mlcd and ptal-printd
are typically called by ptal-init,
and it's generally unnecessary to invoke them manually from the command
line except for development or debugging purposes.
A ptal-mlcd process exists in one of several overall states.
When startup is complete, it is in the inactive state.
When an application tries to access the device, ptal-mlcd
starts to activate, or in other words, establish an MLC/1284.4
communication session with the device.
Once that is complete, ptal-mlcd is in the active state.
If a communication error happens, which could be the result of a power-off
or disconnection of the device or a protocol error, ptal-mlcd
deactivates, which includes closing all active application sessions
and returning to the inactive state.
For each ptal-mlcd instance, there is a main process that services
application requests and implements the MLC/1284.4 protocols. When
ptal-mlcd activates as described above, it forks a sub-process which
handles low-level I/O to/from the device and passes data to/from the main
process using anonymous pipes. In addition, when operating in composite
USB mode, it forks a separate sub-process to service application connections
to the raw 7/1/2 print interface outside of the MLC/1284.4 session; this
sub-process exits when the application closes the connection or when
ptal-mlcd special-cases the PML (Peripheral Management Language)
service on the device, by keeping open a single PML channel while
activated, virtualizing application requests to open the PML service, and
multiplexing PML requests (gets and sets but not yet traps) from possibly
multiple applications over a single PML session with the device. On
the other hand, if you specify the -nopml switch, then this
behavior is disabled, and only one application may open PML at a time
but has exclusive access to the device's PML service.
The typical invocation for ptal-mlcd on parallel, set up
automatically by "ptal-init setup",
is something like the following (split into multiple lines for clarity):
ptal-mlcd mlc:par:OfficeJet_K80 [-remconsole] \
-devidmatch "MDL:OfficeJet K80;" \
-devidmatch "SERN:000000000010;" \
-base 0x378 -basehigh 0x778 -device /dev/lp0
Specifying the -device switch is recommended if your system has
kernel parallel-port support, because it gives ptal-mlcd a
mechanism to lock the parallel port and prevent other processes from
opening it and interfering with its signalling. Just make sure you
specify the right device node, or it won't do any good. Also, unlike
for USB, do not specify multiple device nodes, including using wildcards.
In order to take advantage of ptal-mlcd's new hardware-assisted
parallel-port I/O, you must configure your parallel port to ECP in your
The typical invocation for ptal-mlcd on USB, set up
automatically by "ptal-init setup",
is something like the following (split into multiple lines for clarity):
ptal-mlcd mlc:usb:OfficeJet_K80 [-remconsole] \
-devidmatch "MDL:OfficeJet K80;" \
-devidmatch "SERN:000000000010;" \
This means that whenever ptal-mlcd tries to activate, it will open
up each USB printer device node in turn (including the implicitly-globbed
libusb devices), search
the device ID string for the specified string(s) (normally the model field
and possibly also the serial number field), and use the first matching
device it finds.
Alternatively, if you only have one USB-connected printer device, you can
use a simpler command line such as the following:
ptal-mlcd mlc:usb:0 -device /dev/usb/lp0 -noglobusb [-remconsole]
-noglobusb is required in this case to prevent the implicit globbing
of libusb devices and
guarantee that you'll really get the device you want.
This usage is not recommended, however, because if you add another USB
printer, you'll find that the /dev/usb/lp0 device node
assignment may change over time, depending on what order the devices are
connected and powered on. Even if you have multiple USB printers with
only one connected and powered on at a time, having a separate instance
of the daemons for each model makes it easier to have different print
queues/drivers and scanning profiles set up for the different devices
Raw printing directly to the 7/1/2 print interface (on a composite-USB
device) is very slow if done through
libusb instead of
libusb and/or Linux
usbdevfs don't indicate how many bytes were successfully transferred
at a timeout condition, which can happen if the printer is busy or out of
paper. Therefore, this communication path had to be reduced to sending only
one "runt" USB packet (63 or 31 bytes) at a time, to prevent data corruption
when recovering from timeout conditions.
ptal-mlcd includes the following user-interface features:
ptal-mlcd supports the following "virtual" services through
- Special "virtual" services
- Log messages to standard output and syslog (/var/log/messsages)
- A debug console
ptal-mlcd logs various kinds of messages to standard output
(which may not be visible unless started in a particular terminal window)
and syslog (/var/log/messsages):
- PTAL-MLCD-CONSOLE -- The debug console, if enabled with the
- PTAL-MLCD-PID -- The main process ID of this instance of
- PTAL-MLCD-CMDLINE -- The command line passed to this instance of
- PTAL-MLCD-DEVNODE -- The device node, if any, in use by this
instance of ptal-mlcd
- PTAL-MLCD-DUMP -- A dump of the current state of most internal
data structures (useful for debugging)
- PTAL-MLCD-GLOB-DEVNODES -- Used by
"ptal-init setup" to glob standard
and libusb device nodes
(ptal-mlcd must be in a deactivated state)
Here is a sample log message followed by an explanation of the components:
- FATAL ERROR -- An internal error bad enough to cause
ptal-mlcd to exit. Normal behavior if a startup error (such
as command-line syntax error or problem setting up socket) occurred.
Otherwise, should never happen. If it does, especially if it's preceeded
by a large data-structure dump, then please report the problem to the
- ERROR -- An error, such as a loss of communication with the
device, which is (hopefully) recoverable without exiting. Probably
not a bug, with the exception of an fdRegister error, which
should be reported to the
- WARNING -- An unusual situation that could potentially
indicate a problem, but which is (hopefully) immediately recoverable
with no adverse effect.
- SYSLOG -- An information message by default logged only to
syslog, not standard output, indicating successful startup or activation.
- ENTRY, EXIT, INFO -- Debug messages.
ptal-mlcd: ERROR at ExMgr.cpp:871, dev=<mlc:usb:OfficeJet_K80@/dev/usb/lp0>, pid=17306, e=19 t=1064276197
ptal-mlcd's debug console is accessible in either of the following
When the debug console is active, the following commands may be used:
- ERROR indicates the severity (see above for the possibilities).
- ExMgr.cpp:871 indicates the source-code file and line number
where the message was logged. Useful for debugging.
- dev=<mlc:usb:OfficeJet_K80@/dev/usb/lp0> indicates the
device name controlled by the ptal-mlcd instance that logged the
message as well as the device node (if one is currently open). In contrast,
this might look like dev=<mlc:usb:OfficeJet_K80@%001%006>
if the device was being accessed through
- pid=17306 indicates the process ID that logged the message.
- e=10 indicates the current value of the errno
variable, probably irrelevant unless it's an error message complaining
about a failed system call.
- t=1064276197 is a timestamp of when this message was logged,
in the form of number of seconds since the beginning of the Unix epoch
(January 1, 1970).
- exClose(reason=0x0010) is the specific message with parameter
substitution as appropriate. Some log messages, especially some fatal
errors that should never happen (but occasionally they do:-) don't even
have a customized second-line message.
- dump -- Dumps all data structures.
- pid -- Prints the current main process ID.
- activate -- Starts activation if not active. Prints return
code of exActivate(), which may be 0 if already active, 1 if
started (and probably finished) activating, and -1 if an error occurred.
- deactivate -- Deactivates by calling exClose(),
which always logs an error message.
- log -- Enables all log messages, including debug messages.
- logwarn -- Enables log messages other than debug messages
(ENTRY, EXIT, and INFO).
- nolog -- Returns to the default log level of logging
ERROR and "FATAL ERROR" to standard output and syslog
and SYSLOG to syslog but not standard output.
ptal-mlcd creates Unix-domain sockets in the directory
/var/run/ptal-mlcd with the filename based on the first command-line
parameter, such as par:OfficeJet_Series_700 or
When libptal first opens the Unix-domain socket, it exchanges
several request/reply packets with ptal-mlcd, which might include
getting the device ID string, service name to socket ID lookup, socket ID to
service name ("reverse") lookup, and channel open. In most cases,
ptal-mlcd attempts to activate if it's not already active before
processing one of these requests. After a successful open reply, for all
practical purposes the connection is then a pass-through connection to the
requested service on the device.
ptal-mlcd maintains a fixed-maximum-size table of various kinds
of "session" structures. Whenever a new connection is received from the
named socket, it is assigned to a "command session", which handles the
request/reply command interaction with libptal. When an open
request is received, the command session is linked with either a
"transport session" or virtual "PML session", depending on whether the
open request was for a peripheral socket ID corresponding to the PML
service (which is virtualized) or for a different service. As the open
is processed the linked sessions go through several state transitions
together. If/when the open succeeds, the connection is fully transferred
from the command session to the transport or PML session, but if the
open fails then the linked session is freed and the connection stays
with the command session ready for possibly other commands.
ptal-mlcd depends on the /dev/null "bit-bucket" device
for several purposes. First of all, it substitutes /dev/null for
the standard input, output, and/or error file descriptors if they aren't
already open, which is the case when invoked from a hotplug script (not
Second, it substitutes /dev/null file descriptors for sessions which
don't have an associated file descriptor (such as the master PML session)
or sessions which have been closed by libptal but are not ready
to be completely torn down by ptal-mlcd (such as when
ptal-mlcd gets an error writing to a session's file descriptor,
which is not the right state for tearing down the session). The basic
requirements for the /dev/null device are that select()
should indicate ready to read and write, read() should return an
end-of-file condition (zero bytes read), and write() should always
"succeed" in writing all of the requested bytes.
- ParPort -- Handles access to parallel ports with PC-style
- port type detection
- parallel-port register access (for efficiency assumes PC-style parallel
port accessed with the x86/Alpha I/O port read/write instructions)
- IEEE 1284-1994 signalling:
- negotiation into ECP and nibble modes
- termination back to compatibility mode
- ECP forward-to-reverse and reverse-to-forward
- ECP forward data transfers
- ECP and nibble reverse data transfers
- ECP hardware-assisted data transfers
- Reading the device ID string
- QueueEntry, Queue -- Node and container base classes
for a doubly-linked list (derived by message, watchdog timer, "BDR",
and lookup classes).
- ExMsgHandler -- Base class for ExMgr,
ExTransport, and ExTransportChannel classes that serves
as a template for receiving messages (either dispatched explicitly or as a
result of a watchdog timer).
- ExMsg -- Contains destination and parameter information to
route a message to the correct ExMsgHandler-derived class.
- ExMsgQueue -- Used to store free (available) or pending messages.
- PolledTimer -- After being "started" with a particular timeout
or delay, is polled periodically to determine if the timeout has passed.
Also contains utility functions to delay a particular amount of time by
blocking without polling.
- ExWatchdogTimer -- Once set with a timeout and started,
"pops" if the timeout happens before being cancelled. This class takes
two different forms, which determine the meaning of "pop":
- A message-based timer has an ExMsg and an ExMsgHandler
destination set. When the timer pops, the message is sent to the destination.
Ordered in the ExWatchdogTimerQueue based on time remaining until pop.
- A periodic timer has no ExMsg or ExMsgHandler set and is
used only by various modules to request a maximum select() timeout.
Ordered in the ExWatchdogTimerQueue based on timeout.
- ExCountingWatchdogTimer -- ExWatchdogTimer-derived
class that starts the underlying timer every time a start request is
received, and doesn't stop it until the same number of stop requests
have been received.
- ExWatchdogTimerQueue -- Queues ExWatchdogTimer objects
in the order they will pop.
- ExBdr -- "Buffer Data Record" -- Contains data buffer, offset,
length, and owning transport channel (if any). BDRs for a single packet
(i.e. header and multiple data BDRs) may be chained together to ensure
uninterrupted transmission. Note that this is not the same as several
BDRs stored in a queue, although in some situations the chain is broken
up and each BDR queued separately.
- ExBdrQueue -- Stores BDRs containing data waiting to be
processed (except when used by an ExBufferPool).
- ExBufferPool -- Stores free (available) BDRs. Uses a "lazy
allocation" algorithm, in that BDRs aren't allocated until they're needed,
to reduce memory consumption in the average case.
- ExSessionLookup -- ExLookup-derived class that stores
an scd attribute missing from the ExLookup class (see below).
- ExMgr -- Base class for ParMgr and UsbMgr that
manages lots of things:
- buffer pool
- transport (see below)
- main runtime loop (exMain()) that revolves around
- file descriptor sets for select() (from console, socket,
sessions, and LLIO)
- command-line processing
- message queue and pool
- timer queues
- debug console
- named Unix-domain socket
- application sessions (command, transport, and virtual PML)
- low-level I/O ("LLIO"), partially implemented in derived classes
- ParMgr -- ExMgr-derived class for parallel-port
connections to glue ParPort functionality into ExMgr.
- UsbMgr --ExMgr-derived class for USB connections to
implement device enumeration, device ID retrieval, and
- ExLookup, ExLookupQueue -- Stores parameters/results
of requests for 1284.4 service name to socket ID (and vice versa) lookup.
- ExCounter -- Maintains a counter up to MAXINT (-1, to prevent
- ExDebugCounter -- ExCounter-derived class that only
increments for debug purposes, and otherwise just does a plain set.
- ExCreditCounter -- Maintains "credit" count for MLC/1284.4
and maximum outstanding forward packets.
- ExTransportChannel -- Handles the basic logic of forward and
reverse data flow and reverse buffer management for a single data channel
between the host and peripheral.
Base class for ExMlcTransportChannel.
- ExTransport -- Pass-through (raw) transport, manages a list of
ExTransportChannel-derived objects, and provides non-channel-specific
- ExMlcCreditCounter -- ExCreditCounter-derived class
that sets the maximum credit count of 0xFFFF.
- ExMlcTransportChannel -- ExTransportChannel-derived
class that handles the specifics of an MLC/1284.4 channel, including channel
open/close, forward/reverse data flow, and credit management.
- ExMlcCommandChannel -- ExMlcTransportChannel-derived
class that handles sending/receiving of MLC/1284.4 command packets and
special credit management needed for the command channel.
- ExMlcTransport -- ExTransport-derived class that
manages the channel list consisting of one ExMlcCommandChannel
object and many ExMlcTransportChannel objects, MLC/1284.4 packet
header overhead, routing reverse data to the correct channel, negotiating
the protocol revision (MLC or 1284.4), and service name lookup.
The ExTransport classes were ported directly from the USB I/O
firmware of the HP JetDirect 175X external print server, which is a
VxWorks-based embedded system. The porting task was greatly simplified
because the code has almost no system-level dependencies. It was designed
to run in a task context with other code, so it doesn't block on anything.
The rest of the necessary functionality was largely re-written for Linux
and (for better or for worse) dumped into the ExMgr class, with
an auxilliary ParPort class.
The following system-level assumptions are made by ptal-mlcd:
- "Standard" Unix/Linux libc API and system-call semantics.
It works on Linux and FreeBSD, so from this standpoint it shouldn't be
too hard to port it to other similar operating systems.
- The ParPort class assumes a PC-style parallel-port register
set (with several annoying inverted bits in the control and status
registers), that it can use x86/Alpha
inb and outb operations to access these registers, and
that it can give itself permission to do this in some OS-dependent manner,
currently supported on Linux and FreeBSD. Alternatively, the parallel-port
support can be turned off entirely with the "./configure
- At a minimum, USB support depends on a (presumably kernel-mode)
printer-class device driver which binds to the "7/1/3" USB interface
on the device, which is for MLC or 1284.4 packetized communication,
not raw print data as is the case with 7/1/1 and 7/1/2. Bidirectional
support is required. Also, there needs to be some sort of ioctl()
call to retrieve the device ID string of the currently-attached device,
although it's OK if it's retrieved once on plug-in and cached for subsequent
queries. A read() error needs to be returned when the device is
unplugged or powered off, and by extension, select() needs to
unblock and indicate that the file descriptor is readable in this situation.
- For better USB support, there also need to be ioctl() calls to
query supported protocols (7/1/1, 7/1/2, 7/1/3) and which one is currently
selected, select a different protocol, issue the HP vendor-specific
CHANNEL_CHANGE_REQUEST USB command (in order to support channel-78 reset
and running MLC/1284.4 over 7/1/2 needed for certain models), query the
device's vendor and product IDs, and query the USB bus and device addresses
assigned to the device. The Linux kernels 2.4.19 and 2.5.7 and later
have the necessary functionality for full USB support.
- For best (and most portable) USB support,
libusb is used, either
in conjunction with the above-mentioned kernel printer-class driver for
non-printer-class interfaces, or by itself for all interfaces (recommended
for non-Linux platforms which don't already have sufficient printer-class
driver support). If used in conjunction with a kernel printer-class driver
as above, it's important to have an ioctl() call to query the USB
bus and device addresses assigned to the device, in order to match up the
printer-class driver file descriptor with the appropriate