Device control I/O function handler

Updated: April 19, 2023

Prototype:

int (*devctl) (resmgr_context_t *ctp,
               io_devctl_t *msg,
               RESMGR_OCB_T *ocb ) 

Classification:

I/O

Default handler:

iofunc_devctl_default()

Helper functions:

iofunc_devctl(), iofunc_devctl_verify()

Client functions:

devctl(), ioctl()

Messages:

_IO_DEVCTL

Data structure:

struct _io_devctl {
  uint16_t type;
  uint16_t combine_len;
  int32_t  dcmd;
  uint32_t nbytes;
  int32_t  zero;
};

struct _io_devctl_reply {
  uint32_t zero;
  int32_t  ret_val;
  uint32_t nbytes;
  int32_t  zero2;
};

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

Description:

Performs the device I/O operation as passed from the client's devctl() in dcmd. The client encodes a direction into the top two bits of dcmd, indicating how the devctl() is to transfer data (the “to” field refers to the _POSIX_DEVDIR_TO bit; the “from” field refers to the _POSIX_DEVDIR_FROM bit):
to field from field Meaning
0 0 No data transfer
0 1 Transfer from driver to client
1 0 Transfer from client to driver
1 1 Transfer bidirectionally

In the case of no data transfer, the driver is expected to simply perform the command given in dcmd.

Accessing data sent by the client

If the client provides data, it immediately follows the io_devctl_t header provided by the msg parameter. Before you access any of the data, you need to validate its length.

Because combine messages make determining the length of a message more complicated, validation is simpler when you prohibit their use. (There is no advantage in allowing combine messages.)

To prevent the use of combine messages, check whether ctp->rcvid is zero and return an error if it is.

How you check the message length depends on the length of message that you intend to handle:

If your handler supports multiple device control operations, you might have operations from both categories.

You need to verify that the actual message length is at least as large as the expected length. If you need to access any part of the message to determine the expected size, you need to ensure that ctp->size is large enough to include this data.

After you’ve verified the message length, you need to access the data. If your device control operation is handling short messages only (category 1 above), the message payload is contained entirely in the message buffer. If your operation has to handle large messages (category 2), you probably have to use resmgr_msgget() or resmgr_msggetv() to read at least part of the payload.

Returning data to the client

If you can, before you perform a request, you should validate that the amount of data that will be returned plus the size of the io_devctl_t header is less than or equal to ctp->info.dstmsglen. Otherwise, the client will not receive all the data that you send. If the amount of data your device control operation returns is variable, it is a good idea that the returned data include the size or a means to calculate it.

Structure padding

Note that the input and output data structures are zero-padded so that they align with each other. This means that the implicit data area begins at the same address in the input and output structures.

Unrecognized commands

If using the helper routine iofunc_devctl(), beware that it'll return the constant _RESMGR_DEFAULT in the case where it can't do anything with the devctl() message. This return value is there to decouple legitimate errno return values from an “unrecognized command” return value. Upon receiving a _RESMGR_DEFAULT, the base-layer library will respond with an errno of ENOSYS, which the client's devctl() library function will translate into ENOTTY.

Checking the open mode

It's up to your function to check the open mode against the operation; no checking is done anywhere in either the client's devctl() library or in the resource manager library. For example, it's possible to open a resource manager “read-only” and then issue a devctl() to it telling it to “format the hard disk” (which is very much a “write” operation). It would be prudent to verify the open mode first before proceeding with the operation.

For more information on checking the security and validity of messages, see Permission checking.”

Available dcmd values

Note that the range of dcmd values you can use is limited (0x0000 through 0x0FFF inclusive is reserved for BlackBerry QNX). Other values may be in use; take a look through the include files that have the name <sys/dcmd_*.h>.

Returns:

The status via the helper macro _RESMGR_STATUS() and the reply buffer (with reply data, if required).

For an example, take a look at “A simple device control I/O function handler example,” below.

Referenced by:

resmgr_io_funcs_t I/O table

Permission checking:

The default implementation iofunc_devctl_default() handler does not perform permission checking because this handler does not implement any functionality that needs protecting. A resource manager that implements devctl() must apply permission checking according to the command to be executed. This is complicated because of the flexible nature of devctl() operations. For example, when the baud rate of a serial device is changed, it is acceptable to open the device for read-only access and then perform a devctl() to change the baud rate. Although this operation performs a modification, it does not require the file descriptor to have been opened for write. Consequently, each individual devctl() command often needs to have appropriate permission checks.

Possible methods for dealing with devctl() include the following strategies:

Note: QNX does not recommend using iofunc_devctl_verify() to perform validation.

For an example handler that includes permission and length checking, see A simple device control I/O function handler example.