A simple device control I/O function handler example
#include <sys/types.h>
#include <unistd.h>
#include <devctl.h>
int 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). You may also find that you need to change the sample rate for recording sound from
22kHz to 44.1kHz depending on the application. 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 offer all the needed functionality 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:
- fd
- The file descriptor of the resource manager that you're sending the devctl() to.
- dcmd
- The command itself—a combination of two bits worth of direction, and 30 bits worth of command (see the discussion below).
- dev_data_ptr
- A pointer to a data area that can be sent to, received from, or both.
- nbytes
- The size of the dev_data_ptr data area.
- dev_info_ptr
- 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 Device control I/O function handler
).
/*
* io_devctl1.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#include <devctl.h>
#define AUDIO_DAC 1
#define AUDIO_ADC 2
extern void audio_set_nchannels(unsigned num);
extern uint32_t audio_get_samplerate(unsigned port);
extern void audio_set_samplerate(unsigned port, unsigned rate);
#define DCMD_AUDIO_SET_CHANNEL_MONO __DION(_DCMD_MIXER, 1)
#define DCMD_AUDIO_SET_CHANNEL_STEREO __DION(_DCMD_MIXER, 2)
#define DCMD_AUDIO_GET_SAMPLE_RATE_DAC __DIOF(_DCMD_MIXER, 4, uint32_t)
#define DCMD_AUDIO_SET_SAMPLE_RATE_DAC __DIOT(_DCMD_MIXER, 5, uint32_t)
#define DCMD_AUDIO_GET_SAMPLE_RATE_ADC __DIOF(_DCMD_MIXER, 6, uint32_t)
#define DCMD_AUDIO_SET_SAMPLE_RATE_ADC __DIOT(_DCMD_MIXER, 7, uint32_t)
int io_devctl (resmgr_context_t *ctp, io_devctl_t *msg, iofunc_ocb_t *ocb)
{
int sts;
/* 1) Create the reply buffer at the start of ctp->msg so we make
* sure we have enough space */
struct _io_devctl_reply *reply = (struct _io_devctl_reply *)ctp->msg;
/* Create a pointer to the rate variable for the _GET_ devctls. */
unsigned nbytes = 0;
uint32_t *rate = _IO_OUTPUT_PAYLOAD(*reply);
/* 2) See if it's a standard devctl() */
if ((sts = iofunc_devctl_default (ctp, msg, ocb)) != _RESMGR_DEFAULT) {
return (sts);
}
/* How many bytes did the client send in addition to the devctl header? */
size_t payload_size = ctp->size - sizeof(msg->i);
/* 3) See which command it was, and act on it */
switch (msg -> i.dcmd) {
case DCMD_AUDIO_SET_CHANNEL_MONO:
/* Read or write access are sufficient */
if( !(ocb->ioflag & (_IO_FLAG_WR | _IO_FLAG_RD)) ) {
return EPERM;
}
audio_set_nchannels (1);
break;
case DCMD_AUDIO_SET_CHANNEL_STEREO:
/* Read or write access are sufficient */
if( !(ocb->ioflag & (_IO_FLAG_WR | _IO_FLAG_RD)) ) {
return EPERM;
}
audio_set_nchannels (2);
break;
case DCMD_AUDIO_GET_SAMPLE_RATE_DAC:
/* Write access is required for accessing the DAC */
if( !(ocb->ioflag & _IO_FLAG_WR) ) {
return EPERM;
}
/* Verify that the client wants enough bytes */
if( ctp->info.dstmsglen < (sizeof(*reply) + sizeof(uint32_t)) ) {
return EINVAL;
}
/* Set up the reply to the client */
nbytes = sizeof(uint32_t);
*rate = audio_get_samplerate (AUDIO_DAC);
break;
case DCMD_AUDIO_SET_SAMPLE_RATE_DAC:
/* Write access is required for accessing the DAC */
if( !(ocb->ioflag & _IO_FLAG_WR) ) {
return EPERM;
}
/* Verify that we got all the bytes. */
if( payload_size < sizeof(uint32_t) ) {
return EBADMSG;
}
rate = _IO_INPUT_PAYLOAD(msg);
audio_set_samplerate (AUDIO_DAC, *rate);
break;
case DCMD_AUDIO_GET_SAMPLE_RATE_ADC:
/* Read access is required for accessing the ADC */
if( !(ocb->ioflag & _IO_FLAG_RD) ) {
return EPERM;
}
/* Verify that the client wants enough bytes */
if( ctp->info.dstmsglen < (sizeof(*reply) + sizeof(uint32_t)) ) {
return EINVAL;
}
/* Set up the reply to the client */
nbytes = sizeof(uint32_t);
*rate = audio_get_samplerate (AUDIO_ADC);
break;
case DCMD_AUDIO_SET_SAMPLE_RATE_ADC:
/* Read access is required for accessing the ADC */
if( !(ocb->ioflag & _IO_FLAG_RD) ) {
return EPERM;
}
/* Verify that we got all the bytes. */
if( payload_size < sizeof(uint32_t) ) {
return EBADMSG;
}
rate =_IO_INPUT_PAYLOAD(msg);
audio_set_samplerate (AUDIO_ADC, *rate);
break;
/* 4) In case it's a command that we don't recognize, fail it */
default:
return (ENOSYS);
}
/* 5) Tell the client that it worked */
memset(reply, 0, sizeof(*reply));
SETIOV (ctp->iov, reply, sizeof(*reply) + nbytes);
return (_RESMGR_NPARTS (1));
}
- Step 1
- We set up where the reply is going to be written to. The client is expecting the response in an _io_devctl_reply structure, with (depending on the value of dcmd) the sample rate immediately following the structure in memory. This response is going to take sizeof(msg->o) + sizeof(uint32_t) bytes. The buffer that we have and can use for sending replies is ctp->msg and it is ctp->msg_max_size bytes long. Because it's the same buffer that we used for receiving the commands from the client, we need to be careful to finish processing the input before we start writing the output to the buffer. The devctl() message from the client is sitting at offset ctp->offset into the buffer ctp->msg. If we have enough space between ctp->offset and ctp->msg_max_size for the entire reply, we could use that. However, it's much safer to just write our reply at the beginning of the buffer, which gives us the full ctp->msg_max_size bytes of data to write our response.
- Step 2
- We see again the use of a helper function, this time iofunc_devctl_default(),
which is used to perform all default processing for devctl().
The iofunc_devctl_default() function is called when you don't
supply your own device control I/O function handler and let
iofunc_func_init() initialize the I/O and connect
functions tables for you.
We include iofunc_devctl_default() in our device control I/O function handler 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 3
- The checking for our custom devctl() commands is done here. We simply check
the dcmd values that the client code sent to see if there's a
match. Note that we call the fictitious functions
audio_set_nchannels(),
audio_get_samplerate() and
audio_set_samplerate() to accomplish the actual
work
for the client. - Step 4
- 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 5
- If we get to this line, we successfully handled the dcmd passed to
devctl(). We need to respond to the client. 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 value is then returned to the client.
Instead of using the macro _RESMGR_NPARTS(), we could have used the _RESMGR_PTR() macro:
/* 5) tell the client it worked */ memset(reply, 0, sizeof(*reply)); return (_RESMGR_PTR (ctp, reply, sizeof(*reply) + nbytes));
The devctl() command requires us to send a response to the client for devctl() to be successful. It is insufficient to just return EOK without any response message.