UDI driver coding basics

pseudod child channel operations

The routines for the GIO child channel provide entry points for binding and unbinding a channel to the GIO mapper, responding to GIO channel events, and performing the read and write operations,

    * GIO operations
   static void
   pseudo_gio_channel_event(udi_channel_event_cb_t * channel_event_cb)
   	udi_channel_event_complete(channel_event_cb, UDI_OK);

static void pseudo_gio_bind_req(udi_gio_bind_cb_t * gio_bind_cb) { /* * Fake up some constraints so that the GIO mapper can call * UDI_BUF_ALLOC safely */ gio_bind_cb->xfer_constraints.udi_xfer_max = 0; gio_bind_cb->xfer_constraints.udi_xfer_typical = 0; gio_bind_cb->xfer_constraints.udi_xfer_granularity = 1; gio_bind_cb->xfer_constraints.udi_xfer_one_piece = FALSE; gio_bind_cb->xfer_constraints.udi_xfer_exact_size = FALSE; gio_bind_cb->xfer_constraints.udi_xfer_no_reorder = TRUE; udi_gio_bind_ack(gio_bind_cb, 0, 0, UDI_OK); }

static void pseudo_gio_unbind_req(udi_gio_bind_cb_t * gio_bind_cb) { udi_gio_unbind_ack(gio_bind_cb); }

The pseudo_gio_channel_event and pseudo_gio_unbind_req routines are identical to the cmos_child_channel_event and cmos_gio_unbind_req routines used by the uci_cmos driver (see ``udi_cmos child channel operations'').

The pseudo_gio_bind_req establishes some transfer constraints for the GIO channel; this routine is called by the environment to enter the driver code after a udi_gio_bind_req(3udi).

The driver uses the xfer_constraints element of the udi_gio_bind_cb_t(3udi) structure passed to the driver by the environment to establish the data transfer constraints for the buffer that will be used for reads and writes. These constraints are then sent back to the environment in the same gio_bind_cb, via a call to udi_gio_bind_ack(3udi).

Transfer constraints are device-dependent, and are provided so that the UDI environment can allocate buffers appropriately for the data to be transferred. Even though the pseudod driver does not control a real device, it uses buffers to simulate real data transfer.

See udi_xfer_constraints_t(3udi).

Once the GIO channel is bound, the driver essentially waits for the environment to call udi_gio_xfer_req(3udi) and the corresponding pseudod driver entry point, pseudo_gio_xfer_req:

   static void
   pseudo_gio_xfer_req(udi_gio_xfer_cb_t * gio_xfer_cb)
   	pseudo_region_data_t *rdata = UDI_GCB(gio_xfer_cb)->context;

switch (gio_xfer_cb->op) { case UDI_GIO_DIR_WRITE:{ /* * From client (application) to provider (this driver) */ udi_size_t nread = 0; udi_buf_t *buf = (udi_buf_t *)gio_xfer_cb->data_buf; udi_ubit8_t *cp; udi_ubit32_t src_offset = 0;

#if PSEUDO_VERBOSE udi_size_t n; udi_ubit32_t iter = 0;

udi_debug_printf("Size of complete write buffer 0x%x\n", buf->buf_size); #endif while (src_offset < buf->buf_size) { nread = sizeof (rdata->rx_queue); /* Sz of src buffer */ if (buf->buf_size - src_offset < nread) nread = buf->buf_size - src_offset; udi_buf_read(buf, src_offset, /* src offset */ nread, /* Size data to read */ &rdata->rx_queue); /* Destination */ cp = &rdata->rx_queue[0]; #if PSEUDO_VERBOSE udi_debug_printf("\nIteration %d\n" "Size of partial write buffer 0x%x\n", iter++, nread); #endif src_offset += nread;

/* * At this point, * rdata->rx_queue[0] - rdata->rx_queue[nread] * holds 'nread' bytes of data passed down from the * user to us. This would be where we could pass * it to hardware or something. We'll just print * it in hexdump-ish format. FIXME: assumes an ASCII * bytestream and encoding. */ #if PSEUDO_VERBOSE for (n = 0; n < nread; n++, cp++) { udi_ubit8_t c = *cp;

if (!(n & 15)) { udi_debug_printf("\n %04x: ", n); } if (((c >= ' ') && (c <= '~')) || (c == '\t') || (c == '\r') || (c == '\n')) { udi_debug_printf("%c", c); } else { udi_debug_printf("{%2x}", c); } } #endif } /* * Free the buffer locally so the GIO client (mapper) * doesn't have to. */ udi_buf_free(gio_xfer_cb->data_buf); gio_xfer_cb->data_buf = NULL; udi_gio_xfer_ack(gio_xfer_cb); } break;

/* * From provider (us) to client (application) */ case UDI_GIO_DIR_READ: pseudo_gio_do_read(gio_xfer_cb); break;

/* * If the user specified anything else, (including a * UDI_GIO_DIR_READ|UDI_GIO_DIR_WRITE), surrender */ default: udi_gio_xfer_nak(gio_xfer_cb, UDI_STAT_NOT_UNDERSTOOD); break; } }

NOTE: The PSEUDO_VERBOSE sections of the above code are discussed under ``Debugging code in the pseudod driver''.

After setting a pointer to the channel operation context, pseudo_gio_xfer_req branches based on the value of gio_xfer_cb->op passed from the environment. The routine then performs either the operations for UDI_GIO_DIR_READ or UDI_GIO_DIR_WRITE; the driver responds to any other operation from the environment with udi_gio_xfer_nak(3udi) with a status of UDI_STAT_NOT_UNDERSTOOD.

The routine for the UDI_GIO_DIR_WRITE case (that is, writing data from user-level to the pseudo-device), is in the pseudo_gio_xfer_req code, while the read case (reading data from the pseudo device and writing it to user-level) executes another routine, pseudo_gio_do_read, to perform the UDI_GIO_DIR_READ operation. pseudo_gio_do_read is explained further on in this section.

The UDI_GIO_DIR_WRITE section of pseudo_gio_xfer_req begins by setting some local data variables:

counter for the number of bytes read out of the data buffer passed to the driver

pointer to the data buffer

pointer to a region data structure to hold the data read

the current offset into the buffer for the udi_buf_read operation

The main while loop of the routine uses src_offset as a counter to make sure we don't try to read past the end of the data buffer we get from the environment. The variable nread is set to the size of the data buffer; it is reset to the amount of data actually in the buffer in the following two lines of code if the amount of data in the buffer is less than the size of the buffer.

A call to udi_buf_read(3udi) then reads nread bytes of the data buffer strating at src_offset into rdata->rx_queue. The variable cp is set to point to the first byte of the data in rdata->rx_queue, and will be used to write the data to debug output (see ``Debugging code in the pseudod driver''). src_offset is incremented by the size of the data buffer to prepare for the next (possible) read.

In a driver that accessed a real device, this part of the code would use PIO calls to write to the device, as the CMOS RAM driver does via udi_pio_trans(3udi) (see ``udi_cmos child channel operations''). Instead, the data is written to user level via a call to udi_debug_printf(3udi) (see ``Debugging code in the pseudod driver'').

Once the while loop reads and writes the entire buffer, the driver frees the data buffer with udi_buf_free(3udi), sets the pointer to the data buffer to NULL, and lets the environment know it's completed the environment's call to pseudo_gio_xfer_req, using udi_gio_xfer_ack(3udi) and the original udi_xfer_cb passed to the driver.

The UDI_GIO_DIR_READ operation is carried out by pseudo_gio_do_read:

   static void
   pseudo_gio_do_read(udi_gio_xfer_cb_t * gio_xfer_cb)
   	udi_cb_t *gcb = UDI_GCB(gio_xfer_cb);
   	pseudo_region_data_t *rdata = gcb->context;
   	udi_size_t len =
   		(PSEUDO_BUF_SZ - 1 ) <
   		gio_xfer_cb->data_buf->buf_size ? (PSEUDO_BUF_SZ -
   						   1) : gio_xfer_cb->data_buf->

buf_size; udi_size_t i = 0, n;

/* * Write a simple test pattern into the tx_queue. */ while (i < len) { n = udi_snprintf((char *)&rdata->tx_queue[i], (len - i), "%d ", rdata->testcounter);

/* * If the entire sprintf buffer wouldn't fit, lop it off * and issue a partial read. * If the string ends in a space, we know that a complete * number was generated, and thus should not be lopped off. * In other words, the driver will only work correctly all * the time if you read NUMBYTES each time where * NUMBYTES=sprintf(buf,"%d ",-1). * If the driver returns 0 bytes for some read, it is likely * that you are not requesting enough data. Why return 0 * instead of a partial buffer? Because it would get * really messy if we stored the state of the * partially written string in the driver for subsequent * reads. */ i += n; if ((n == 0) || (i == len)) { if (rdata->tx_queue[i - (i == 0 ? 0 : 1)] != ' ') i -= n; else rdata->testcounter++; break; } rdata->testcounter++; }

/* * Tell the mapper how much data is present. */ gio_xfer_cb->data_buf->buf_size = i;

/* * Let the mapper have the data. */ #ifdef GIO_BUF_BUG udi_buf_write(pseudo_buf_written, gcb, rdata->tx_queue, i, gio_xfer_cb->data_buf, 0, i, UDI_NULL_BUF_PATH); #else udi_buf_write(pseudo_buf_written, gcb, rdata->tx_queue, i, gio_xfer_cb->data_buf, 0, 0, UDI_NULL_BUF_PATH); #endif /* GIO_BUF_BUG */ }

This routine essentially generates a test pattern to simulate a read operation from a real device, and passes this back to the GIO mapper (for return to user level).

The driver first sets a udi_cb_t(3udi) control block containing a copy of the udi_gio_xfer_cb_t(3udi) control block passed to it and a local copy of the pointer to region data passed as part of the control block context. len is set to either the length of the actual data in the control block's buffer or the size of the null-terminated holding place in region data (PSEUDO_BUF_SZ - 1).

The following while loop uses the previously initialized variable ``i'' as a counter, with an upper bound of len, to write the amount of data requested to the buffer.

Test data is written to the region data area using udi_snprintf(3udi) (modeled after, but not identical to, snprintf(3S)).

   udi_snprintf((char *)&rdata->tx_queue[i], (len - i),
   "%d ", rdata->testcounter);
[In a driver that accessed a real device, this part of the code would use PIO calls to read the device, as the CMOS RAM driver does via udi_pio_trans(3udi) (see ``udi_cmos child channel operations'')].

After the test data is written to the holding place in region data, the size of the data written is placed into gio_xfer_cb->data_buf->buf_size, which will be passed to the GIO mapper via udi_buf_write:

   udi_buf_write(pseudo_buf_written, gcb, rdata->tx_queue, i,
   	      gio_xfer_cb->data_buf, 0, 0, UDI_NULL_BUF_PATH);

pseudo_buf_written is a callback routine that the environment will execute once the udi_buf_write completes; it is explained below. gcb is a pointer to a copy of the gio_xfer_cb passed to the driver as part of the environment's call to pseudo_gio_do_read. rdata->tx_queue is the region data element holding the data to be written to the buffer. The next argument, i, holds the size of the data to be written.

gio_xfer_cb->data_buf is a pointer to the target data buffer. This will have been allocated by the environment before its call to pseudo_gio_do_read. The following two arguments, both zeros, tell the environment to write the source data from rdata->tx_queue to the beginning of the buffer and to use the fourth argument, i, as the length of the data to write. Since we specified a non-NULL data buffer, there's no need for udi_buf_write to allocate a new one, so the final argument is UDI_NULL_BUF_PATH.

See udi_buf_write(3udi) and udi_buf_copy(3udi) for detailed explanations of these arguments and their usage.

Once the environment completes the udi_buf_write, it returns the new buffer back to the driver via the callback routine pseudo_buf_written:

   static void
   pseudo_buf_written(udi_cb_t *gcb,
   		   udi_buf_t *new_buf)
   	udi_gio_xfer_cb_t *xfer_cb = UDI_MCB(gcb, udi_gio_xfer_cb_t);
   	udi_size_t bufsize;

xfer_cb->data_buf = new_buf; bufsize = new_buf->buf_size;

#if PSEUDO_VERBOSE udi_debug_printf("pseudo_buf_written: data_len = %d\n", bufsize); #endif udi_gio_xfer_ack(xfer_cb); }

The purpose of thsi call is to issue an appropriate udi_gio_xfer_ack back to the environment to acknowledge completion of the pseudo_gio_xfer_req call that began the UDI_GIO_DIR_READ operation.

This routine first converts the generic control block it gets from the environment to the type it needs ( udi_gio_xfer_cb_t(3udi)) for the udi_gio_xfer_ack.

The bufsize variable is used, if PSEUDO_VERBOSE is set when the driver is compiled, to print the buffer size to debug output (see ``Debugging code in the pseudod driver'').

The routine then passes the new buffer with its contents back to the environment by setting xfer_cb->data_buf = new_buf and using xfer_cb as the argument to udi_gio_xfer_ack(3udi).

The UDI environment deals appropriately with the data buffer, such as returning its contents to a user-level application that requested a pseudod device read.

Debugging code in the pseudod driver

To summarize the operation of the pseudod driver as invoked from user-level (see UNRESOLVED XREF-0), a UDI_GIO_DIR_READ operation is generated by the UDI environment, to which the driver responds by writing a test pattern to the user level.

If the driver is compiled with PSEUDO_VERBOSE set, then the UDI_GIO_DIR_WRITE routine will write the same test pattern back to user level as debug output (simulating a write to a real device). Other information is also written to user-level by debugging statements in the code of the pseudo_gio_do_read and pseudo_buf_written routines discussed under ``pseudod child channel operations''.

The PSEUDO_VERBOSE sections of code all make use of the udi_debug_printf(3udi) routine to print to user level the current values of various variables. See Debugging Services in the UDI Core Specification for an explanation of how to use this and the other debugging service routines provided by the UDI environment.

NOTE: Note that calls to debugging service routines such as udi_debug_printf are not expected to be found in compiled, production UDI drivers. In particular, be aware that the UDI Core Specification permits a conforming UDI implementation to deal with calls to udi_debug_printf in an implementation-defined manner, including ignoring them.

In the case of the UDI_GIO_DIR_WRITE code in pseudo_gio_do_read, debugging output is used to simulate writing to a real device:

   for (n = 0; n < nread; n++, cp++) {
   	udi_ubit8_t c = *cp;

if (!(n & 15)) { udi_debug_printf("\n %04x: ", n); } if (((c >= ' ') && (c <= '~')) || (c == '\t') || (c == '\r') || (c == '\n')) { udi_debug_printf("%c", c); } else { udi_debug_printf("{%2x}", c); } }

cp is the pointer to region data holding the buffer data to be ``written'' to the ``device''. After doing some range and value checking on the buffer data, it's written in an appropriate format via udi_debug_printf.

Next topic: pseudod Management Agent channel operations
Previous topic: pseudod parent channel operations

© 2005 The SCO Group, Inc. All rights reserved.
OpenServer 6 and UnixWare (SVR5) HDK - 19 June 2005