                       QNX 4 Device Drivers 

Introduction

When you need to provide an interface to a traditional form of
character device (modem, terminal or printer) which will be
accessed by programs which expect to see a 100% POSIX 1003.1
interface, then you probably need to write a Dev Driver process. 
The POSIX interface includes services such as modem control, line
discipline, baud rates, parity, echo, line editing, timed reads,
interbyte timing, etc.  All of these services are provided for
and managed by the QNX4 Dev process.  QNX4 Dev Drivers simply
provide the hardware interface between Dev and the hardware using
a private, highly optimized interface.  The only responsibility
of a QNX4 Dev Driver is to provide access to the hardware.  The
real work is done by Dev itself.

Before we start, it should be pointed out that Dev Drivers are
not the only method of interfacing to hardware devices in QNX4. 
In fact, unless your device looks very much like a typical serial
device, you probably don't want to write a Dev Driver.

Many types of hardware are better handled in QNX4 by a server
process which responds to custom messages directly from client
processes.  Remember that any process can access the hardware if
it has sufficient privity, so such server processes are
remarkably easy to develop, and are often much easier to develop
than a Dev Driver (see the Client/Server technical note for more
information).

If the best way to access a piece of hardware is deemed to be
through standard I/O calls (open, close, read and write), then a
message-driven client/server approach may not be appropriate. 
Nonetheless, a Dev Driver may still not be the best approach.  A
special form of server process can be written which adopts a
portion of the QNX4 namespace (eg: /dev/tape) and which responds
to a well defined set of I/O messages (IO_OPEN, IO_CLOSE, etc.) 
This type of server will be an I/O Manager and can provide highly
optimized I/O directly to the hardware.  Such servers allow for
specialized non-POSIX functionality and this approach avoids the
possibly unnecessary overhead of communicating through another
process; Dev. (see the I/O Manager technical note for more
information).

This technical note addresses the case where POSIX style
character I/O is required.  This includes a vast number of
devices such as multiport, intelligent serial adapters,
windows-based terminal emulators, printer drivers and many, many
more.

Overview

QNX is a modular operating system.  As such, various system
services are provided by independent server processes.  In the
case of POSIX 1003.1 compatible devices (character special
files), I/O services are provided by a common Device Server
process called Dev.

Dev owns the part of the namespace beginning with the prefix
'/dev' and receives messages from processes which wish to do I/O
operations on pathnames beginning with this prefix.  All POSIX
features and side-effects are managed exclusively by Dev.
including blocking and non-blocking reads and writes, timed read
operations, flow control, echo, line editing, etc.



The interface to the hardware devices themselves is provided by a
series of Dev Driver processes.  These drivers communicate
directly with the hardware and interface with Dev via a set of
three data queues as shown in Figure 1 below:

     Dev             output buffer                 Driver
                     +------------------------+          
    ³///³----------->³||||||||||||||||||||||||³--->³\\\\³
    ³///³            +------------------------+    ³\\\\³
    ³///³                                          ³\\\\³
    ³///³            input buffer                  ³\\\\³
    ³///³            +-----------------------+     ³\\\\³
    ³///³<------+----³|||||||||||||||||||||||³<----³\\\\³
    ³///³       |    +-----------------------+     ³\\\\³
    ³///³       |                                  ³\\\\³
    ³///³       +-------------------------+        ³\\\\³
    ³///³                                 |        ³\\\\³
    ³///³       canonical (edit) buffer   |        ³\\\\³
    ³///³            +------------+       |        ³\\\\³
    ³///³<-----------³||||||||||||³<------+        ³\\\\³
    ³///³            +------------+                ³\\\\³

Figure 1:  Communication via queues between Dev and a Device
           Driver
                                     
The function of a QNX4 Dev Driver (unlike that of a more classic
UNIX device driver, or even a QNX2 device driver) is simply to
transmit data bytes to the hardware device and to receive data
bytes from the hardware and insert them into the raw input queue. 
Aside from a few (optional) control functions, there is little
else required of a QNX4 Dev Driver.

QNX4 Dev Drivers are normal QNX processes compiled to run at
privity 1 in order to gain access to the hardware I/O registers. 
These drivers can be started and stopped dynamically.

When a driver starts, it attempts to register itself and allocate
resources from Dev by calling the dev_drvr_register () function. 
At this time the driver indicates how many devices it will be
handling, what prefix the devices will be given and some
housekeeping information.  Provided that there is sufficient
resources available, Dev will reply with a driver control
structure which contains generic information which will be used
by the driver in subsequent calls.  Included in this structure is
the address of a function to call (called tti) which must be used
to add data into the input data queue.  Included in the
parameters passed to the dev_drvr_register () function is the
address of three routines in the driver's space which can be
called by Dev to indicate significant events.  The Kick ()
function will be called by  Dev whenever something happens with
the output buffer (ie: data available).  The Stty () function is
called by Dev whenever something happens to the termios (device
control) structure which requires the attention of the driver
(such as change in baud rate or parity).  The Ioctl () function
is called to perform general control operations (sendbreak,
dropline, etc.)

The driver must then call dev_drvr_mount () for each of the
devices.  In this call, the driver must supply the individual
sizes for each of the input, output and canonical buffers.  Each
call to dev_drvr_mount () can include one or more devices.  If
more than one device is specified, then the library routine will
attempt to pack the I/O buffers into as few shared memory
segments as possible which is a good way to conserve system
resources.  Provided that the resources can all be acquired, Dev
will reply with a device control structure for each device.  This
structure will contain pointers to the three data queues as well
as a unique tty# (handle) for each device.  This tty# must be
used when calling the tti () function to identify which input
queue is to receive the data.

Finally, once all devices are registered and mounted, it is
necessary to enable (arm) each of the output channels by calling
the dev_drvr_arm () function for each device.  This will complete
the registration process and will allow Dev to start placing data
in the output queues.

The driver is now ready for its mainline mode of operation.

A final function is available for releasing all of the resources
assigned to a Dev Driver before it terminates.  The
dev_drvr_shutdown () call is used just before a driver
terminates.  Dev Drivers should catch the SIGTERM signal and
perform an orderly shutdown which allows the slay utility to be
used to cleanly terminate a driver.

Processing Input Data

Most device drivers in QNX4 will be interrupt driven when it
comes to input data.  For this reason, the facilities provided by
Dev are geared towards allowing efficient and convenient data
input directly from interrupt handlers.

QNX4 interrupt handlers are capable of triggering a proxy when
they return.  The tti() function accepts as parameters a tty#
(indicating the device), a 16-bit input value comprised of 8 data
bits and 8 control bits, and a value which can be used to access
Dev's data segment from within the context of the interrupt
handler.  The function tti() will either return a zero or it
will return the process-id of a proxy which should be triggered
to wake up Dev.  An interrupt handler simply has to return (in
ax) the return value of tti() to have this effect.  If the tti()
function is called at process time, then it will be necessary
to Trigger() the proxy if a non-zero value is returned.

The tti() function has been designed to be as efficient as
possible and simply places the data into the raw input queue.  It
returns either a zero or a proxy if a pre-conditioned event has
been triggered.  Dev set up these pre-conditions based on pending
I/O requests.  Some of these conditions depend on the
relationship between received data bytes and timers and may in
fact cause further conditions to be armed, so it is necessary to
call tti() for every byte received.

The bottom byte of the data word passed to tti() is the
character received.  The top byte contains flags indicating
special conditions:

          Good Data     00000000       data
          H/W Break     10000000     00000000
          Bad Data      01000DFP       data
          H/W Event     00100000     0000chnf

                    D-Data Overrun   c-carrier received
                    F-Framing Error  h-hangup (loss of carrier)
                    P-Parity Error   n-hardware handshaking ON
                                     f-hardware handshaking OFF


Processing Output Data

Output starts when the Kick() function is called.  It is the
responsibility of the driver to examine the output buffer on
every Kick() call and attempt to output any data which is
available.  A pointer to the output buffer which has changed is
passed as the only argument Kick().

Control Operations

Dev will call the Stty() function when it requires the driver to
change some aspect of the hardware (such as baud rate, data bits,
stop bits or parity).  The new settings can be found using the
pointer to a termios structure which is passed as the only
argument to Stty().

Dev will call the Ioctl() function whenever it requires some
special action of the driver (such as assert or remove a break
condition, or start/stop a dropline condition).  It will also
call Ioctl() to provide the driver with useful information (such
as first open or last close).

Special Modes and Options

A flag word is passed as one of the arguments to
dev_drvr_register() which can contain bits indicating special
modes of operation.  These bits are described in the manual page
for the dev_drvr_register() function.

Compiling the Driver

The interface between Dev and a Dev Driver is designed to consume
as few resources as possible while still maintaining a high speed,
secure environment.  Both Dev and the Dev Driver far-call into the
other's code space. 

While operating in this code space, the interface makes available
one data segment for accessing global variables.  The
dev_driver_register() process ensures that LDT aliases exist for
the appropriate code and data segments.  It should be noted that
the following restrictions apply within the interface functions
Kick(), Stty() and Ioctl():

1.   Only global variables in the same data segment as the
     variable drv_ctrl (which was passed to dev_drvr_register) can be
     accessed.

2.   Only functions in the same code segment as the interface
     routine (Kick, Stty or Ioctl) can be called.

3.   In general, no system functions or library routines can be
     used other than Trigger().

4.   When an interface routine is called by Dev, it will be using
     the stack of Dev.  Thus stack usage should be kept to a minimum
     and one should not assume that SS=DS.

5.   Stack checking must be disabled.

The above rules are easiest to enforce if the Dev Driver is
compiled in 16-bit (or 32-bit) small model with stack checking
disabled.  As a rule of thumb, the interface routines should do
nothing more than modify global variables, optionally trigger a
proxy, then return.  If more work must be done, it is recommended
that a proxy be triggered which will wake up the Dev Driver process
which can then perform the bulk of the work at process time. This
avoids having to deal with the above restrictions.

Dev Drivers must be compiled with privity 1 (-T1).

                         Library Routine Reference 

dev_drvr_register
----------------------------------------------------------- 	

Synopsis

     #include <termios.h>
     #include <sys/_devdrvr.h>

     int dev_drvr_register(
          char *name,
          char *prefix,
          int nttys,
          int (far *Kick) ( struct output_buffer far *),
          int (far *Stty) (struct termios far *)
          int (far *Ctrl) (union ioctl_entry far *)
          struct driver_ctrl *dry_ctrl,
          unsigned flags );

Description

The dev_drvr_register function is used to create new character
special devices.

'name' is a symbolic name to be associated with this class of
device.  This will be the string found in the driver_type member
of a _dev_info_entry which is returned by dev_info and is
displayed as the device Type by the stty utility.  Name cannot
exceed 8 characters.

'prefix' is the string to be used as the first part of a device
name.  All devices created as a result of this call will have
names in the /dev directory which start with this prefix and end
in a number (eg: registering 3 devices with the prefix 'tst' will
create 3 devices called '/dev/tst1', '/dev/tst2' and '/dev/tst3'. 
prefix cannot exceed 8 characters.

In versions 4.23 and later of Dev/Devdrivers 
'prefix' may be a simple 1 to 8 character name (eg: "tst") or
it can be a pathname (eg: "/dev/tst") in which case the leading
portion of the pathname will be used to identify which Dev manager
to connect to (assuming more than one are running, see the -N option
of Dev). In addition, the prefix may specify a device unit number as
well (eg: "/dev/ser3") in which case Dev will register the block of devices
starting at the specified offset provided that there is no conflict with
any other devices using the same prefix.

'nttys' is the number of devices (or units) to be created with this
prefix.

'drv_ctrl' is a pointer to a driver_ctrl structure which will be
filled if the function is successful.  This structure contains
information which is common to all the newly created devices. 
The structure is defined in <sys/_devdrvr.h> and includes the
following members:

     short int base_tty         The 'internal' tty number assigned
                                to the first device.  The remaining
                                devices are assigned sequential
                                numbers.

     short int nttys            The number of devices belonging to
                                this handle.

     pid_t (far *tti)           A far pointer to a function (inside
     (int tty, int c,           Dev's address space) which must be
      unsigned ds)              used to enter data into the input
                                queue. The first argument is the
                                internal tty number of the device
                                (base + unit - 1).  The second argument
                                is the received data character+status. The
                                third argument is a data selector to
                                use when calling tti().  The function
                                returns either a zero (do nothing) or
                                a (constant) proxy which should be Triggered
                                to 'kick' Dev.

     pid_t (far *ttim)          A far pointer to a function (inside
     (int tty, int len,         Dev's address space) which can be
      char far *data,           used to enter a block of good data into the
      unsigned ds)              input queue. The first argument is the
                                internal tty number of the device
                                (base + unit - 1).  The second argument
                                is the number of received characters. The
                                third argument is a far pointer to the data.
                                The fourth third argument is a data selector to
                                use when calling tti ().  The function
                                returns either a zero (do nothing) or a
                                (constant) proxy which should be Triggered to
                                'kick' Dev. This function is only available to
                                32 bit drivers (see DRVR_SUPP_TTIM in flags).
                                Note that tti() must be used to enter any
                                status/error codes.

     short unsigned tti_ds      A data selector to be used as the third
                                argument to tti().

     pid_t hw_pid               A proxy to trigger Dev when a significant
                                event occurs.

     short unsigned flags       A bitfield of flag bits associated with
                                this new device class.  The bits are
                                defined in <sys/_devdrvr.h>.  At the
                                time of this printing the following bits
                                are defined:

                                DRVR_GETS_PIDS    IOCTL_OPEN passes down
                                                  pid, fd, etc. (this
                                                  feature did not exist
                                                  until QNX 4.10).

                                DRVR_IS32         32 bit Dev Subsystem.

                                DRVR_SUPP_TTIM    Indicates that ttim() is
                                                  available to be called.

'flags' is used to control optional modes of driver operation.
The flag bits are defined in <sys/_devdrvr.h>.  At the time
of this printing, the following driver flags are defined:

     DRVR_SCAN                  open () with no suffix causes Dev to
                                scan for an unused unit.  (eg: /dev/con
                                free console).

     DRVR_NOTTY                 Not a terminal device (eg: /dev/null).
                                If set, this flag causes the togetattr
                                () function to fail, which cause isatty
                                () to fail. 

     DRVR_LOCK_HFLOW            IHFLOW/OHFLOW in c_cflag cannot be
                                changed, even by stty.

     DRVR_SINGLE_OPEN           Open () will fail if the device is
                                already opened.

     DRVR_HEX_SUFFIX            Suffixes are 00 based hex rather than 1
                                based decimal (eg: Dev.pty).

     DRVR_STTY                  Driver gets called on every tcsetattr()
                                call rather than just when baud/stop-bits/
                                parity are changed.

     DRVR_NO_SUFFIX             Suppress the numeric suffix if only	1
                                device is registered (eg: /dev/null
                                will be displayed instead of /dev/null1).

'Kick' is the address of a function to be called by Dev every time
something new happens with the output queue (new data or flow
control change).  Kick will be passed a single parameter which is
a far pointer to the output buffer which has changed.

'Stty' is the address of a function to be called by Dev every time
something significant happens to the termios control structure
associated with this device.  Unless the DRVR_STTY mode is
enabled, significant events are deemed to be only those which
require some attention by the hardware (baud rate, parity, etc.). 
Stty will be passed a single parameter which is a far pointer to
the termios structure which has changed.

'Ctrl' is the address of a function to be called by Dev to perform
control (or housekeeping) operations.  Ctrl will be passed a
single parameter which is a far pointer to an ioctl_entry which
is a multi-purpose structure containing a control operation.  In
general, it is permissible to simply ignore these commands, but
more sophisticated drivers may be able to use this information to
provide extra services or features.  The ioctl_entry is defined
in <sys/_devdrvr.h>.  The first short int defines the type of the
operation.  The second short int is usually a unit number (1 ...
N) which indicates which of the devices owned by this driver is
being operated on.  On return, it is expected that the driver
will replace the first word with a status (0 usually meaning
success).  At the time of this printing, the following types are
defined:

     IOCTL_OPEN         Called on first open().
     IOCTL_CLOSE        Called on last close().
     IOCTL_CTL          Called by qnx_ioctl().
     IOCTL_STARTBREAK   Assert break condition (if possible).
     IOCTL_STOPBREAK    Remove the break condition.
     IOCTL_STARTDROP    Lower modem control (ie: drop
                        communication line).
     IOCTL_STOPDROP     Raise modem control (return to normal).

Returns

The dev_drvr_register function returns a (positive integer)
handle to be used with subsequent calls to dev_drvr_mount and
dev_drvr_shutdown.

Errors

When an error has occurred, the function returns a negative value
indicating the type of error that has been detected.

     (-1) Unable to communicate with Dev.
     (-1) Insufficient resources.

See Also

     dev_drvr_mount, dev_drvr_arm, dev_drvr_shutdown

Example

Classification

QNX

dev_drvr_mount
------------------------------------------------------------

Synopsis

     #include <sys/_devdrvr.h>

     int dev_drvr_mount   (int handle,
                           int tty,
                           int nttys,
                           struct device_ctrl *ctrl );

Description

The dev_drvr_mount function is used to create nttys instances of
new character special devices.

The value in tty is the starting internal tty number for the
first device in the group being mounted (based on the base_tty
value returned in the drv_ctrl structure).

An array of initialized device_ctrl entries is passed to this
function (one for each of the nttys).  Each entry on input should
define the following structures:

     short unsigned isize     Size of input buffer to allocate.
     short unsigned osize     Size of output buffer to allocate.
     short unsigned csize     Size of canonical (edit) buffer to
                              allocate.

This call will attempt to allocate all the resources necessary
for this entire group of devices.  Where possible, buffers will
be packed into as few memory selectors as possible, to conserve
resources.  The buffers and control structures will all be made
addressable by both this driver and Dev.

If successfully mounted, the function will return a zero and the
remaining device control entries will be filled for each device. 
At the time of this printing, the following members are defined:

     short int                   tty       Internal tty number
                                           of this unit.
     struct input_buffer   far   *ibuf     Pointer to raw input
                                           buffer.
     struct output_buffer  far   *obuf     Pointer to raw
                                           output buffer.
     struct canon_buffer   far   *cbuf     Pointer to edit buffer.
     struct termios        far   *termios  Pointer to device
                                           control structure.
     struct stat           far   *stat     Pointer to device's
                                           directory entry.

The value of tty in this device_ctrl structure is the value to
use as the first argument to tti () when entering data for this
unit.

obuf is a pointer to an output buffer control structure which
contains information required for outputting data.

ibuf, cbuf, termios and stat are all provided for informational
purposes, but need not actually be used directly by the driver.

Returns

The dev_drvr_mount function returns a zero if unsuccessful.


dev_drvr_arm
--------------------------------------------------------------

Synopsis

     #include <sys/_devdrvr.h>
     int dev_drvr_arm( int tty );

Description

The dev_drvr_arm function is used to enable a newly created
character special device.  No data will be placed in the (already
established) device output buffer by Dev until this call is made.

'tty' is the internal tty number (base+unit-1) of the device being
enabled.

Returns

The dev_drvr_arm function returns a zero if successful

Errors

When an error has occurred, the function returns a negative value
indicating the type of error that has been detected.

     (-1) No prior call to dev_drvr_register.
     (-1) Error communicating with Dev.
     (-1) Bad tty.

See Also

dev_drvr_register, dev_drvr_mount, dev_drvr_shutdown

Example

Classification

QNX

When an error has occurred, the function returns a negative value
indicating the type of error that has been detected.

     (-1) No prior call to dev_drvr_register.
     (-2) Unable to allocate the memory for the requested device
          buffers.
     (-4) Bad handle.
     (-4) Unable to install the new devices.

See Also

dev_drvr_register, dev_drvr_arm, dev_drvr_shutdown

Example

Classification

QNX


dev_drvr_shutdown
--------------------------------------------------------------

Synopsis

     #include <sys/_devdrvr.h>
     int dev_drvr_shutdown (unsigned handle);

Description

The dev_drvr_shutdown function is used to release all resources
associated with the character special devices associated with
handle.  This function must be called before a device driver
terminates.

Returns

The dev_drvr_shutdown function returns zero if unsuccessful.

Errors

When an error has occurred, the function returns a negative value
indicating the type of error that has been detected.

(-1) No prior call to dev_drvr_register.
(-1) Bad handle.

See Also

dev_drvr_register, dev_drvr_mount, dev_drvr_arm

Example

Classification

QNX
