Sample code for handling _IO_DEVCTL messages

You can add the following code samples to either of the /dev/null examples provided in the Simple device resource manager examples section of the Bones of a Resource Manager chapter. Both of those code samples provided the name /dev/sample. With the changes indicated below, the client can use devctl() to set and retrieve a global value (an integer in this case) that's maintained in the resource manager.

The first addition defines what the devctl() commands are going to be. This is generally put in a common or shared header file:

typedef union _my_devctl_msg {
        int tx;             /* Filled by client on send */
        int rx;             /* Filled by server on reply */
} data_t;

#define MY_CMD_CODE      1
#define MY_DEVCTL_GETVAL __DIOF(_DCMD_MISC,  MY_CMD_CODE + 0, int)
#define MY_DEVCTL_SETVAL __DIOT(_DCMD_MISC,  MY_CMD_CODE + 1, int)
#define MY_DEVCTL_SETGET __DIOTF(_DCMD_MISC, MY_CMD_CODE + 2, union _my_devctl_msg)

In the above code, we defined three commands that the client can use:

MY_DEVCTL_SETVAL
Sets the server's global variable to the integer the client provides.
MY_DEVCTL_GETVAL
Gets the value of the server's global variable and puts it into the client's buffer.
MY_DEVCTL_SETGET
Sets the server's global variable to the integer that the client provides, and then returns the previous value of the server's global variable in the client's buffer.

Add this code to the main() function:

/* For handling _IO_DEVCTL, sent by devctl() */
io_funcs.devctl = io_devctl;

And the following code gets added before the main() function:

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

int global_integer = 0;

Now, you need to include the new handler function to handle the _IO_DEVCTL message (see the text following the listing for additional notes):

int io_devctl(resmgr_context_t *ctp, io_devctl_t *msg,
              RESMGR_OCB_T *ocb) {
    int     nbytes, status, previous;

    union {  /* See note 1 */
        data_t  data;
        int     data32;
        /* ... other devctl types you can receive */
    } *rx_data;
    

    /*
     Let common code handle DCMD_ALL_* cases.
     You can do this before or after you intercept devctls, depending
     on your intentions.  Here we aren't using any predefined values,
     so let the system ones be handled first. See note 2.
    */
    if ((status = iofunc_devctl_default(ctp, msg, ocb)) !=
         _RESMGR_DEFAULT) {
        return(status);
    }
    status = nbytes = 0;

    /*
     Note this assumes that you can fit the entire data portion of
     the devctl into one message.  In reality you should probably
     perform a MsgReadv() once you know the type of message you
     have received to get all of the data, rather than assume
     it all fits in the message.  We have set in our main routine
     that we'll accept a total message size of up to 2 KB, so we
     don't worry about it in this example where we deal with ints.
    */

    /* Get the data from the message. See Note 3. */
    rx_data = _DEVCTL_DATA(msg->i);

    /*
     Three examples of devctl operations:
     SET: Set a value (int) in the server
     GET: Get a value (int) from the server
     SETGET: Set a new value and return the previous value
    */
    switch (msg->i.dcmd) {
    case MY_DEVCTL_SETVAL: 
        global_integer = rx_data->data32;
        nbytes = 0;
        break;

    case MY_DEVCTL_GETVAL: 
        rx_data->data32 = global_integer; /* See note 4 */
        nbytes = sizeof(rx_data->data32);
        break;
        
    case MY_DEVCTL_SETGET: 
        previous = global_integer; 
        global_integer = rx_data->data.tx;

        /* See note 4. The rx data overwrites the tx data
           for this command. */

        rx_data->data.rx = previous;
        nbytes = sizeof(rx_data->data.rx);
        break;

    default:
        return(ENOSYS); 
    }

    /* Clear the return message. Note that we saved our data past
       this location in the message. */
    memset(&msg->o, 0, sizeof(msg->o));

    /*
     If you wanted to pass something different to the return
     field of the devctl() you could do it through this member.
     See note 5.
    */
    msg->o.ret_val = status;

    /* Indicate the number of bytes and return the message */
    msg->o.nbytes = nbytes;
    return(_RESMGR_PTR(ctp, &msg->o, sizeof(msg->o) + nbytes));
}

Here are the notes for the above code:

  1. We define a union for all the possible types of received data. The MY_DEVCTL_SETVAL and MY_DEVCTL_GETVAL commands use the data32 member, and the MY_DEVCTL_SETGET uses the data member, of type data_t, which is a union of the received and transmitted data.
  2. The default devctl() handler is called before we begin to service our messages. This allows normal system messages to be processed. If the message isn't handled by the default handler, then it returns _RESMGR_DEFAULT to indicate that the message might be a custom message. This means that we should check the incoming command against commands that our resource manager understands.
  3. The data to be passed follows directly after the io_devctl_t structure. You can get a pointer to this location by using the _DEVCTL_DATA(msg->i) macro defined in <devctl.h>. The argument to this macro must be the input message structure: if it's the union message structure or a pointer to the input message structure, the pointer won't point to the right location.

    For your convenience, we've defined a union of all of the messages that this server can receive. However, this won't work with large data messages. In this case, you'd use resmgr_msgread() to read the message from the client. Our messages are never larger than sizeof( int) and this comfortably fits into the minimum receive buffer size.

  4. The data being returned to the client is placed at the end of the reply message. This is the same mechanism used for the input data, so we can use the _DEVCTL_DATA() function to get a pointer to this location. With large replies that wouldn't necessarily fit into the server's receive buffer, you should use one of the reply mechanisms described in the Methods of returning and replying section in the Handling Read and Write Messages chapter. Again, in this example, we're only returning an integer that fits into the receive buffer without any problem.
  5. The last argument to the devctl() function is a pointer to an integer. If this pointer is provided, then the integer is filled with the value stored in the msg->o.ret_val reply message. This is a convenient way for a resource manager to return simple status information without affecting the core devctl() operation. It's not used in this example.

If you add the following handler code, a client should be able to open /dev/sample and subsequently set and retrieve the global integer value:

int main(int argc, char **argv) {
    int     fd, ret, val;
    data_t  data;

    if ((fd = open("/dev/sample", O_RDONLY)) == -1) {
            return(1);
    }

    /* Find out what the value is set to initially */
    val = -1;
    ret = devctl(fd, MY_DEVCTL_GETVAL, &val, sizeof(val), NULL);
    printf("GET returned %d w/ server value %d \n", ret, val);

    /* Set the value to something else */
    val = 25;
    ret = devctl(fd, MY_DEVCTL_SETVAL, &val, sizeof(val), NULL);
    printf("SET returned %d \n", ret);

    /* Verify we actually did set the value */
    val = -1;
    ret = devctl(fd, MY_DEVCTL_GETVAL, &val, sizeof(val), NULL);
    printf("GET returned %d w/ server value %d == 25? \n", ret, val);

    /* Now do a set/get combination */
    memset(&data, 0, sizeof(data));
    data.tx = 50;
    ret = devctl(fd, MY_DEVCTL_SETGET, &data, sizeof(data), NULL);
    printf("SETGET returned with %d w/ server value %d == 25?\n",
           ret, data.rx);

    /* Check set/get worked */
    val = -1;
    ret = devctl(fd, MY_DEVCTL_GETVAL, &val, sizeof(val), NULL);
    printf("GET returned %d w/ server value %d == 50? \n", ret, val);

    return(0);
}