Handling devctl() messages

The devctl() function is a general-purpose mechanism for communicating with a resource manager. Clients can send data to, receive data from, or both send and receive data from a resource manager. The prototype of the client devctl() call is:

int devctl( int fd,
            int dcmd, 
            void * data, 
            size_t nbytes, 
            int * return_info);

The following values (described in detail in the devctl() documentation in the QNX Neutrino C Library Reference) map directly to the _IO_DEVCTL message itself:

struct _io_devctl {
        uint16_t                  type;
        uint16_t                  combine_len;
        int32_t                   dcmd;
        uint32_t                  nbytes;
        int32_t                   zero;
/*      char                      data[nbytes]; */
};

struct _io_devctl_reply {
        uint32_t                  zero;
        int32_t                   ret_val;
        uint32_t                  nbytes;
        int32_t                   zero2;
/*      char                      data[nbytes]; */
    } ;

typedef union {
        struct _io_devctl         i;
        struct _io_devctl_reply   o;
} io_devctl_t;

As with most resource manager messages, we've defined a union that contains the input structure (coming into the resource manager), and a reply or output structure (going back to the client). The io_devctl resource manager handler is prototyped with the argument:

io_devctl_t *msg

which is the pointer to the union containing the message.

The type member has the value _IO_DEVCTL.

The combine_len field has meaning for a combine message; see the Combine Messages chapter.

The nbytes value is the nbytes that's passed to the devctl() function. The value contains the size of the data to be sent to the device driver, or the maximum size of the data to be received from the device driver.

The most interesting item of the input structure is the dcmd argument that's passed to the devctl() function. This command is formed using the macros defined in <devctl.h>:

#define _POSIX_DEVDIR_NONE        0
#define _POSIX_DEVDIR_TO          0x80000000
#define _POSIX_DEVDIR_FROM        0x40000000
#define __DIOF(class, cmd, data)  ((sizeof(data)<<16) + ((class)<<8) + (cmd) + _POSIX_DEVDIR_FROM)
#define __DIOT(class, cmd, data)  ((sizeof(data)<<16) + ((class)<<8) + (cmd) + _POSIX_DEVDIR_TO)
#define __DIOTF(class, cmd, data) ((sizeof(data)<<16) + ((class)<<8) + (cmd) + _POSIX_DEVDIR_TOFROM)
#define __DION(class, cmd)        (((class)<<8) + (cmd) + _POSIX_DEVDIR_NONE)

It's important to understand how these macros pack data to create a command. An 8-bit class (defined in <devctl.h>) is combined with an 8-bit subtype that's manager-specific, and put together in the lower 16 bits of the integer.

The upper 16 bits contain the direction (TO, FROM) as well as a hint about the size of the data structure being passed. This size is only a hint put in to uniquely identify messages that may use the same class and code but pass different data structures.

In the following example, a command is generated to indicate that the client is sending data to the server (TO), but not receiving anything in return. The only bits that the library or the resource manager layer look at are the TO and FROM bits to determine which arguments are to be passed to MsgSend().

struct _my_devctl_msg {
    ...
}

#define MYDCMD  __DIOT(_DCMD_MISC, 0x54, struct _my_devctl_msg) 
Note: The size of the structure that's passed as the last field to the __DIO* macros must be less than 214 == 16 KB. Anything larger than this interferes with the upper two directional bits.

The data directly follows this message structure, as indicated by the /* char data[nbytes] */ comment in the _io_devctl structure.