A simple io_devctl() example

The client's devctl() call is formally defined as:

#include <sys/types.h>
#include <unistd.h>
#include <devctl.h>

devctl (int fd,
        int dcmd,
        void *dev_data_ptr,
        size_t nbytes,
        int *dev_info_ptr);

We should first understand this function before we look at the resource manager side of things. The devctl() function is used for "out of band" or "control" operations. For example, you may be writing data to a sound card (the actual digital audio samples that the sound card should convert to analog audio), and you may decide that you need to change the number of channels from 1 (mono) to 2 (stereo), or the sampling rate from the CD-standard (44.1 kHz) to the DAT-standard (48 kHz). The devctl() function is the appropriate way to do this. When you write a resource manager, you may find that you don't need any devctl() support at all and that you can perform all the functionality needed simply through the standard read() and write() functions. You may, on the other hand, find that you need to mix devctl() calls with the read() and write() calls, or indeed that your device uses only devctl() functions and does not use read() or write().

The devctl() function takes these arguments:

The file descriptor of the resource manager that you're sending the devctl() to.
The command itself—a combination of two bits worth of direction, and 30 bits worth of command (see discussion below).
A pointer to a data area that can be sent to, received from, or both.
The size of the dev_data_ptr data area.
An extra information variable that can be set by the resource manager.

The top two bits in the dcmd encode the direction of data transfer, if any. For details, see the description in the I/O reference section (under io_devctl()).

When the _IO_DEVCTL message is received by the resource manager, it's handled by your io_devctl() function. Here is a very simple example, which we'll assume is used to set the number of channels and the sampling rate for the audio device we discussed above:

 * io_devctl1.c

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


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

    // 1) see if it's a standard devctl()
    if ((sts = iofunc_devctl_default (ctp, msg, ocb)) !=
        return (sts);

    // 2) see which command it was, and act on it
    switch (msg -> i.dcmd) {
        audio_set_nchannels (1);
        audio_set_nchannels (2);
        audio_set_samplerate (44100);
        audio_set_samplerate (48000);

    // 3) in case it's a command that we don't recognize, fail it
        return (ENOSYS);

    // 4) tell the client it worked
    memset (&msg -> o, 0, sizeof (msg -> o));
    SETIOV (ctp -> iov, &msg -> o, sizeof (msg -> o));
    return (_RESMGR_NPARTS (1));
Step 1
In the first step, we see again the use of a helper function, this time iofunc_devctl_default(), which is used to perform all default processing for the devctl() function. If you didn't supply your own io_devctl(), and just let iofunc_func_init() initialize the I/O and connect functions tables for you, the iofunc_devctl_default() function is what would get called.

We include it in our io_devctl() function because we want it to handle all the regular devctl() cases for us. We examine the return value; if it's not _RESMGR_DEFAULT, then this means that the iofunc_devctl_default() function "handled" the request, so we just pass along its return value as our return value.

If the constant _RESMGR_DEFAULT is the return value, then we know that the helper function didn't handle the request and that we should check to see if it's one of ours.

Step 2
This checking is done in step 2 via the switch/case statement. We simply compare the dcmd values that the client code would have stuffed into the second argument to devctl() to see if there's a match. Note that we call the fictitious functions audio_set_nchannels() and audio_set_samplerate() to accomplish the actual "work" for the client.

An important note that should be mentioned here is that we've specifically avoided touching the data area aspects of devctl()—you may be thinking, "What if I wanted to set the sample rate to some arbitrary number n, how would I do that?" That will be answered in the next io_devctl() example below.

Step 3
This step is simply good defensive programming. We return an error code of ENOSYS to tell the client that we didn't understand their request.
Step 4
Finally, we clear out the return structure and set up a one-part IOV to point to it. Then we return a value to the resource manager library encoded by the macro _RESMGR_NPARTS(), telling it that we're returning a one-part IOV. This is then returned to the client. We could alternatively have used the _RESMGR_PTR() macro:
// instead of this
    // 4) tell the client it worked
    memset (&msg -> o, 0, sizeof (msg -> o));
    SETIOV (ctp -> iov, &msg -> o, sizeof (msg -> o));
    return (_RESMGR_NPARTS (1));

// we could have done this
    // 4) tell the client it worked
    memset (&msg -> o, 0, sizeof (msg -> o));
    return (_RESMGR_PTR (ctp, &msg -> o, sizeof (msg -> o)));

The reason we cleared out the return structure here (and not in the io_read() or io_write() examples) is because in this case, the return structure has actual contents! (In the io_read() case, the only data returned was the data itself and the number of bytes read—there was no "return data structure," and in the io_write() case, the only data returned was the number of bytes written.)