Article: Using Neutrino's devctl()

Also available at http://qdn.qnx.com/articles/dec1200/devctl.html

Using Neutrino’s devctl()
By Erick Muis, QNX Software Systems Ltd.

When controlling a general terminal interface, most programs use the POSIX
approach to improve portability. Specifically, the termios.h header (located
in /usr/include) contains definitions used by terminal interfaces for POSIX
compatibility.

But if there are times when you can’t use the POSIX termios routines, you
can use the Neutrino devctl() function call instead. This function is
actually a do-all for many different device control operations.

I often get questions such as “How does the fill-in property for the serial
port get set?” Often these end up being the odd cases where devctl() is
needed.

To clarify what is meant by “fill-in property”, one might look at RTS
(Request to Send) or “Is the Caps Lock on the keyboard currently turned on?”
as specific examples. The term “fill-in property” pertains to properties on
a device, whether it be a keyboard, sound card, or serial port.

The best place to find information on devctl() is the Neutrino Library
Reference. docs. Unfortunately, the docs don’t cover devctl() to its full
extent quite yet. To start becoming more familiar with devctl(), you might
want to look through the dcmd_*.h headers located in /usr/include/sys.

Classes
All the files in /usr/include/sys/dcmd_*.h are considered classes, which
include:

_DCMD_ALL - common (all I/O servers).
_DCMD_FSYS - filesystem managers.
_DCMD_BLK - block I/O managers.
_DCMD_CHR - character devices.
_DCMD_NET - network devices.
_DCMD_MISC - miscellaneous commands.
_DCMD_IP - Internet Protocol.
_DCMD_MIXER - mixer (Audio).
_DCMD_PROC - process manager.
_DCMD_MEM - memory card.
_DCMD_INPUT - input devices.
_DCMD_CAM - low-level CAM (Common Access Method) devices (e.g. disks or
CD-ROMs).
_DCMD_PHOTON - Photon.
Each of these contains values that, when used with devctl(), let you, say,
turn on RTS on your serial port or turn on Caps Lock on your keyboard or
read a sample of audio from a sound card. Looking through these header files
will help greatly. The values in the header files are named in such a way
that finding the defined values for your particular operation shouldn’t be
too difficult.

Example #1: Setting RTS on a serial port
Here’s a quick example of setting and unsetting RTS on a serial port:

// For “devctl()”
#include <devctl.h>
#include <sys/dcmd_chr.h>

// For “open()”
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// For Errors
#include <stdlib.h>
#include <stdio.h>

int check_RTS(int fd);

int main(void)
{
int data = 0, fd = 0;

check_RTS(fd);

if((fd = open ("/dev/ser2", O_RDONLY)) == -1)
{
fprintf(stderr, “Error with open() on /dev/ser2. Make sure
exists.\n”);
perror (NULL);
exit(EXIT_FAILURE);
}


// Let’s turn ON RTS now.
data = _CTL_RTS_CHG | _CTL_RTS;

if (devctl (fd, DCMD_CHR_SERCTL, &data, sizeof(data), NULL))
{
fprintf(stderr, “Error setting RTS.\n”);
perror (NULL);
exit(EXIT_FAILURE);
}
// RTS should now be ON.

check_RTS(fd);

sleep (2);


// Now let’s turn RTS OFF.
data = _CTL_RTS_CHG | 0;

if (devctl (fd, DCMD_CHR_SERCTL, &data, sizeof(data), NULL))
{
fprintf(stderr, “Error setting RTS.\n”);
perror (NULL);
exit(EXIT_FAILURE);
}
// RTS should now be OFF.

check_RTS(fd);

close(fd);

return (1);
}


int check_RTS(int fd)
{
int data = 0;

// Let’s see if RTS is set, tell devctl we are requesting line status
information
// and devctl will then assign data the line status information for us.
Too easy.
if (devctl (fd, DCMD_CHR_LINESTATUS, &data, sizeof(data), NULL))
{
fprintf(stderr, “Error setting RTS.\n”);
perror (NULL);
exit(EXIT_FAILURE);
}

if (data & _LINESTATUS_SER_RTS)

printf(“RTS is SET!\n”);

else

printf(“RTS is NOT set\n”);

return(1);
}

You should be able to quickly see what’s important in this example. The two
main areas of interest are the setting of data and the devctl() call. Note
also the data variable, which is used for both sending and receiving data.
When setting RTS, data is assigned a value, then sent via devctl() to the
device. Yet in the code where it checks to see if RTS is set, we send data
in with any value (zero is clean) and devctl() ASSIGNS data a value that’s
then checked against _LINESTATUS_SER_RTS!

In short, if data equals:

_CTL_RTS_CHG | _CTL_RTS = RTS is ON
_CTL_RTS_CHG = RTS is OFF

The data variable is any value and the DCMD_CHR_LINESTATUS macro is sent to
the device. The data variable gets assigned the Line Status of the device.
This then can be used to determine what lines are on/set high on that
device. To do this, one must check the header file for the device that may
fall under this macro. Under dcmd_chr.h, the following devices are covered
under this macro:

Serial Port
DTR, RTS, CTS, DSR, RI, CD
Keyboard
Scroll/Caps/Num Lock, Shift, CTRL, ALT
Parallel Port
No Error, Selected, Paper Out, No Tack, Not Busy
The value that’s in the header is then bitwise & with the value in data to
see if the value is high for that line.

Example #2: Cycling through Caps Lock, Num Lock, and Scroll Lock
In the example below, we open the device /dev/kbd and we start applying
changes to the Caps Lock, Scroll Lock, and Num Lock properties.

Note the key lines in this example are the same as in the last example; they
focus around the data variable. This value is just a simple integer value
that’s passed into the devctl() function. The data variable is assigned its
values by simply performing a bitwise OR to the predefined values in the
/usr/include/sys/dcmd_chr.h header.

Note the values used in the bitwise OR:

_CONCTL_NUM_CHG (Console Control Num Lock Change) OR’d together with
_CONCTL_NUM (Console Control Num Lock) will then turn on Num Lock.

_CONCTL_NUM_CHG on its own will turn off Num Lock.

In short, if data equals:

_CONCTL_NUM_CHG | _CONCTL_NUM = Num Lock ON
_CONCTL_NUM_CHG = Num Lock OFF

This also applies for the other either/or values in the dcmd_chr.h header.

// For “devctl()”
#include <devctl.h>
#include <sys/dcmd_chr.h>

// For “open()”
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// For Errors
#include <stdlib.h>
#include <stdio.h>


int main(void)
{
int data, fd, toggle = 1;

// Open the device we wish to manipulate.
if((fd = open ("/dev/kbd", O_RDONLY)) == -1)
{
fprintf(stderr, “Error with open() on /dev/kbd. Make sure
exists.\n”);
perror (NULL);
exit(EXIT_FAILURE);
}

while(1)
{
switch(toggle)
{
case 1:
{
// Lets now turn on Num Lock and make sure that Caps and
Scroll lock are turned off.
data = (_CONCTL_NUM_CHG | _CONCTL_NUM) | _CONCTL_CAPS_CHG |
_CONCTL_SCROLL_CHG;
break;
}
case 2:
{
// Turn off Num Lock and now turn on Caps Lock (Scroll lock
is already off).
data = _CONCTL_NUM_CHG | (_CONCTL_CAPS_CHG | _CONCTL_CAPS);
break;
}
case 3:
{
// Turn off Caps lock and turn on Scroll lock (Num lock is
already off).
data = _CONCTL_CAPS_CHG | (_CONCTL_SCROLL_CHG |
_CONCTL_SCROLL);
toggle = 0;
break;
}
}

// Explanation below.
if (devctl (fd, DCMD_CHR_SERCTL, &data, sizeof(data), NULL))
{
fprintf(stderr, “Error setting KBD.\n”);
perror (NULL);
exit(EXIT_FAILURE);
}

sleep(1);
toggle++;
}

return (1);
}

Here’s a quick explanation of the devctl() command called:

devctl (fd, DCMD_CHR_SERCTL, &data, sizeof(data), NULL)

The first parameter, fd, is simple - this is the file descriptor of the
device that’s being changed.

The second parameter is the device class that’s being changed. In this case,
it’s a character device DCMD_CHR with a (let’s call them a “subclass”)
subclass of SERCTL. In the docs for devctl(), each class is mentioned. Note
also that each class is a header file located in /usr/include/sys/dcmd
*.h.

The third parameter is the data variable - this is the OR’d value.

The sizeof(data) is pretty straightforward.

The last parameter is actually (from the docs) int* dev_info_ptr. This
allows the device to return additional information instead of just a success
or failure. In this example, we don’t care about any additional information.

Example #3: duration example
In this code, tcdropline(), which is used to disconnect a communications
line, is using devctl():

#include <termios.h>
#include <devctl.h>
#include <errno.h>
#include <sys/dcmd_chr.h>

int tcdropline(int fd, int duration) {
duration = ((duration ? duration : 300) << 16) | _SERCTL_DTR_CHG | 0;

if(devctl(fd, DCMD_CHR_SERCTL, &duration, sizeof duration, 0) == -1) {
if(errno == ENOSYS) {
errno = ENOTTY;
}
return -1;
}
return 0;
}

Hopefully, this article will give you more insight into devctl(). Again, the
best way to communicate to a serial port would be to follow the POSIX
approach via the termios functions. But in cases where this isn’t possible,
devctl() can be a good alternative!