An io_devctl() example that deals with data

In the previous io_devctl() example, above, we raised the question of how to set arbitrary sampling rates. Obviously, it's not a good solution to create a large number of DCMD_AUDIO_SET_SAMPLE_RATE_* constants—we'd rapidly use up the available bits in the dcmd member.

From the client side, we'll use the dev_data_ptr pointer to point to the sample rate, which we'll simply pass as an integer. Therefore, the nbytes member will simply be the number of bytes in an integer (4 on a 32-bit machine). We'll assume that the constant DCMD_AUDIO_SET_SAMPLE_RATE is defined for this purpose.

Also, we'd like to be able to read the current sampling rate. We'll also use the dev_data_ptr and nbytes as described above, but in the reverse direction—the resource manager will return data into the memory location pointed to by dev_data_ptr (for nbytes) instead of getting data from that memory location. Let's assume that the constant DCMD_AUDIO_GET_SAMPLE_RATE is defined for this purpose.

Let's see what happens in the resource manager's io_devctl(), as shown here (we won't discuss things that have already been discussed in the previous example):

/*
 * io_devctl2.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <devctl.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>

#define DCMD_AUDIO_SET_SAMPLE_RATE                      1
#define DCMD_AUDIO_GET_SAMPLE_RATE                      2

int
io_devctl (resmgr_context_t *ctp, io_devctl_t *msg,
           iofunc_ocb_t *ocb)
{
    int     sts;
    int     nbytes;

    // 1) Declare a pointer to the data area of the message.
    void    *data;

    if ((sts = iofunc_devctl_default (ctp, msg, ocb)) !=
        _RESMGR_DEFAULT)
    {
        return (sts);
    }

    // 2) Preset the number of bytes that we'll return to zero.
    nbytes = 0;

    // Check for all commands; we'll just show the ones we're
    // interested in here.
    switch (msg -> i.dcmd) {

    // 3) Process the SET command.
    case    DCMD_AUDIO_SET_SAMPLE_RATE:
        data = _DEVCTL_DATA (msg->i);
        audio_set_samplerate (* (int *) data);
        break;

    // 4) Process the GET command.
    case    DCMD_AUDIO_GET_SAMPLE_RATE:
        data = _DEVCTL_DATA (msg->o);
        * (int *) data = audio_get_samplerate ();
        nbytes = sizeof (int);
        break;
    }

    // 5) Return data (if any) to the client.
    memset (&msg -> o, 0, sizeof (msg -> o));
    msg -> o.nbytes = nbytes;
    SETIOV (ctp -> iov, &msg -> o, sizeof (msg -> o) + nbytes);
    return (_RESMGR_NPARTS (1));
}
Step 1
We've declared a void * called data that we're going to use as a general purpose pointer to the data area. If you refer to the io_devctl() description above, you'll see that the data structure consists of a union of an input and output header structure, with the data area implicitly following that header. We'll use the _DEVCTL_DATA() macro (see the entry for iofunc_devctl() in the QNX Neutrino C Library Reference) to get a pointer to that data area.
Step 2
Here we need to indicate how many bytes we're going to return to the client. Simply for convenience, I've set the nbytes variable to zero before doing any processing—this way I don't have to explicitly set it to zero in each of the switch/case statements.
Step 3
Now for the "set" command. We call the fictitious function audio_set_samplerate(), and we pass it the sample rate which we obtained by dereferencing the data pointer (which we "tricked" into being a pointer to an integer. Well, okay, we didn't trick it, we used a standard C language typecast.) This is a key mechanism, because this is how we "interpret" the data area (the client's dev_data_ptr) according to the command. In a more complicated case, you may be typecasting it to a large structure instead of just a simple integer. Obviously, the client's and resource manager's definitions of the structure must be identical—the best place to define the structure, therefore, is in the .h file that contains your DCMD_* command code constants.
Step 4
For the "get" command in step 4, the processing is very similar (with the typecast), except this time we're writing into the data structure instead of reading from it. Note that we also set the nbytes variable to correspond to the number of bytes that we want to return to the client. For more complicated data accesses, you'd return the size of the data area (i.e., if it's a structure, you'd return the size of the structure).
Step 5
Finally, to return data to the client, we need to note that the client is expecting a header structure, as well as the return data (if any) to immediately follow the header structure. Therefore, in this step, we clear out the header structure to zeros and set the number of bytes (the nbytes member) to the number of bytes that we're returning (recall we had pre-initialized this to zero). Then, we set up a one-part IOV with a pointer to the header and extend the size of the header by the number of bytes we're returning. Lastly, we simply tell the resource manager library that we're returning a one-part IOV to the client.

Important note

Recall the discussion in the io_write() sample above, about the data area following the header. To recap, we stated that the bytes following the header may or may not be complete (i.e., the header may or may not have been read in its entirety from the client), depending on how much data was read in by the resource manager library. Then we went on to discuss how it was inefficient to try to "save" a message pass and to "reuse" the data area.

However, things are slightly different with devctl(), especially if the amount of data being transferred is fairly small (as was the case in our examples). In these cases, there's a good chance that the data has in fact been read into the data area, so it is indeed a waste to reread the data. There is a simple way to tell how much space you have: the size member of ctp contains the number of bytes that are available for you starting at the msg parameter. The size of the data area beyond the end of the message buffer that's available is calculated by subtracting the size of the message buffer from the size member of ctp:

data_area_size = ctp -> size - sizeof (*msg);

Note that this size is equally valid when you are returning data to the client (as in the DCMD_AUDIO_GET_SAMPLE_RATE command).

For anything larger than the allocated region, you'll want to perform the same processing we did with the io_write() example (above) for getting data from the client, and you'll want to allocate a buffer to be used for returning data to the client.