UDI driver coding basics

udi_cmos parent channel operations

First, the udi_cmos code defines the channel operations for the channel between the primary region and the bus bridge. This channel and its operations vectors were defined previously under ``Bus bridge metalanguage entry points''.

   static void
   cmos_parent_channel_event(udi_channel_event_cb_t *channel_event_cb)
   	udi_bus_bind_cb_t *bus_bind_cb;
   	switch (channel_event_cb->event) {

bus_bind_cb = UDI_MCB(channel_event_cb-> params.parent_bound.bind_cb, udi_bus_bind_cb_t);

/* Keep a link back to the channel event CB for the ack. */ UDI_GCB(bus_bind_cb)->initiator_context = channel_event_cb;

/* Bind to the parent bus bridge driver. */ udi_bus_bind_req(bus_bind_cb); break; default: udi_channel_event_complete(channel_event_cb, UDI_OK); } }

The cmos_parent_channel_event operation responds to an event at the other end of the bus bridge channel; the bus bridge mapper generates a call to the cmos_parent_channel_event entry point, with an appropriately filled udi_channel_event_cb_t structure defining the event.

If the channel_event_cb->event element is UDI_CHANNEL_BOUND, The routine first converts the channel_event_cb->params.bound.bind_cb structure to the type required for the udi_bus_bind_req call later in the routine. It does this using the UDI_MCB macro call defined in Control Block Management in the Core Specification.

A pointer to the channel_event_cb is saved so it can be accessed by the cmos_bus_bind_ack operation. Then, a binding back to the bus bridge is requested using udi_bus_bind_req and the converted bus_bind_cb control block (see above). See udi_bus_bind_req(3udi), and udi_bus_bind_cb_t(3udi).

Otherwise, if channel_event_cb->event is not UDI_CHANNEL_BOUND, then a call to udi_channel_event_complete indicates to the bridge mapper that the event was processed successfully (see Inter-module Communication in the Core Specification for the definition of udi_channel_event_complete and udi_channel_event_cb_t).

The bridge mapper responds to the udi_bus_bind_req call with a call to the cmos_bus_bind_ack channel operation, listed below.

   static void cmos_bus_bind_ack_1(udi_cb_t *, udi_pio_handle_t);

static void cmos_bus_bind_ack( udi_bus_bind_cb_t *bus_bind_cb, udi_dma_constraints_t constraints, udi_ubit8_t preferred_endianness, udi_status_t status) { cmos_region_data_t *rdata = bus_bind_cb->gcb.context; udi_channel_event_cb_t *channel_event_cb = bus_bind_cb->gcb.initiator_context;

/* * Don't need to do anything with preferred_endianness, since * our device doesn't do DMA. Even if it did, * preferred_endianness is only used with bi-endianness devices * that can change endianness. */ udi_dma_constraints_free(constraints);

/* * Save the bus bind control block for unbinding later. */ rdata->bus_bind_cb = bus_bind_cb;

if (status != UDI_OK) { udi_channel_event_complete( channel_event_cb, UDI_STAT_CANNOT_BIND); return; }

/* * Now we have access to our hardware. Set up the PIO mappings * we'll need later. */ udi_pio_map(cmos_bus_bind_ack_1, UDI_GCB(bus_bind_cb), CMOS_REGSET, CMOS_BASE, CMOS_LENGTH, cmos_trans_read, sizeof cmos_trans_read / sizeof(udi_pio_trans_t), UDI_PIO_NEVERSWAP|UDI_PIO_STRICTORDER, CMOS_PACE, 0); }

static void cmos_bus_bind_ack_2(udi_cb_t *, udi_pio_handle_t);

static void cmos_bus_bind_ack_1( udi_cb_t *gcb, udi_pio_handle_t new_pio_handle) { cmos_region_data_t *rdata = gcb->context;

/* Save the PIO handle for later use. */ rdata->trans_read = new_pio_handle;

udi_pio_map(cmos_bus_bind_ack_2, gcb, CMOS_REGSET, CMOS_BASE, CMOS_LENGTH, cmos_trans_write, sizeof cmos_trans_write / sizeof(udi_pio_trans_t), UDI_PIO_NEVERSWAP|UDI_PIO_STRICTORDER, CMOS_PACE, 0); }

static void cmos_bus_bind_ack_2( udi_cb_t *gcb, udi_pio_handle_t new_pio_handle) { cmos_region_data_t *rdata = gcb->context; udi_channel_event_cb_t *channel_event_cb = gcb->initiator_context;

/* Save the PIO handle for later use. */ rdata->trans_write = new_pio_handle;

#if DO_INTERRUPTS /* Attach interrupts here... */ #endif

/* Let the MA know we've completed binding. */ udi_channel_event_complete(channel_event_cb, UDI_OK); }

The cmos_bus_bind_ack channel operation is called by the bridge mapper (in response to a udi_bus_bind_req from the driver) to send the status of the bind request back to the driver. It is called with The cmos_bus_bind_ack routine needs to take appropriate actions based on the status given by the mapper.

The bridge mapper calls cmos_bus_bind_ack with the same bus_bind_cb control block passed to it via cmos_bus_bind_req from the driver, a DMA constraints vector, an indication of the most effective endianness to use for the bus, and the status of the previously requested bind operation.

Since this driver does not do any DMA, the DMA constraints are freed via a call to udi_dma_constrins_free, and the preferred_endianness argument is simply ignored.

The bus_bind_cb control block passed to the driver is first used to update the beginning of the region data structure (rdata) to the context element passed as part of bus_bind_cb to the driver. This is the context from the driver's earlier call to cmos_bus_bind_req, and was passed back to the driver as part of the cmos_bus_bind_ack.

The cmos_udi driver's region data structure cmos_region_data_t was defined earlier in the driver code; see ``Region data definitions''.

Similarly, the context of the channel when the event was generated by the bridge mapper, the initiator_context, is saved at the beginning of a channel_event_cb structure. This will be used later in the event the bind was unsuccessful.

The entire bus_bind_cb control block is then saved in the rdata region data structure for later use when unbinding the channel.

Next, the status argument passed in the call to cmos_bus_bind_ack is checked, and if the status is anythingother than UDI_OK, then a call to udi_channel_event_complete acknowledges the failureof the bind to the bridge mapper, using the channel_event_cb control block containing the initiator_context of the channel event. See udi_channel_event_complete(3udi).

If status is UDI_OK, then the channel to the bus bridge is bound and we can access the CMOS RAM device. The udi_pio_map routine is called to map the device memory and registers for access by the driver. Two PIO handles must be allocated: one for reading the device and one for writing to it.

Since udi_pio_map is an asynchronous service call to the environment, and may not return immediately, a ``callback'' function is provided with the call. When the environment allocates the PIO handle, it returns it to the driver by invoking the callback routine.

In the udi_cmos driver, the PIO handle for the write operation is requested in the code for the cmos_bus_bind_ack routine. The cmos_bus_bind_ack_1 routine is specified as the callback function; when the environment allocates the handle, it calls cmos_bus_bind_ack_1 to return it to the driver. The cmos_bus_bind_ack_1 routine saves the context of the call and the write transaction's PIO handle, and then requests a handle for the read transaction using a callback function of cmos_bus_bind_ack_2.

The cmos_bus_bind_ack_2 saves the PIO handle for the write operation in the region data structure. Finally, cmos_bus_bind_ack_2 issues a udi_channel_event_complete call to tell the environment that the bind operation is completed.

The two udi_pio_map calls use similar arguments (in order of appearance):

The function to be called once the requested handle is allocated. These are different with each call to provide the mappings through successive calls to udi_pio_map.



The same three constants are specified for both calls; these were defined earlier in the driver code; see ``Programmed I/O (PIO)''.

The two transaction lists for the read and write transactions were defined earlier in the driver code (see ``PIO transaction lists for cmos_udi''). These transaction lists specify the actions taken when udi_pio_trans is called with the associated PIO handle. [This is done in the child channel operations.]

The number of elements in the trans_list.

The data translation and ordering requirements for accessing device memory, in and OR'd list of constants; see udi_pio_map(3udi) for the full list of attributes. This list is specific to a device, and so is the same for both read and write transactions in this driver. UDI_PIO_NEVERSWAP specifies access to data with no byte swapping. Transactions are limited to one byte or less in size if this flag is set. UDI_PIO_STRICTORDER specifies that the CPU must issue the transactions in order, as the programmer specified.

The PIO pacing time, in microseconds, for this handle. The environment guarantees that any device access that occurs using this PIO handle will be followed by at least pace microseconds before another access occurs to the same device register set (via any handle). (The UDI_PIO_STRICTORDER attribute is required for non-zero pace values.)

We also need a channel operation entry point for the environment to use to respond to a udi_bus_unbind_req from the driver, so the bridge mapper can send the status of the unbind request back to the driver.

The bridge mapper calls cmos_bus_bind_ack with the same bus_bind_cb control block passed to it via cmos_bus_unbind_req from the driver.

   static void
   cmos_bus_unbind_ack(udi_bus_bind_cb_t *bus_bind_cb)
   	udi_mgmt_cb_t *cb = bus_bind_cb->gcb.initiator_context;
   	cmos_region_data_t *rdata = UDI_GCB(bus_bind_cb)->context;

udi_pio_unmap(rdata->trans_read); udi_pio_unmap(rdata->trans_write);


udi_devmgmt_ack(cb, 0, UDI_OK); }

This routine first saves the context of the operation in a udi_mgmt_cb_t(3udi) control block for later use in the acknowlegdement operation, and gets the region data from the control block passed to it. It then frees the region data memory blocks (using udi_pio_unmap(3udi)) and the control block (using udi_cb_free(3udi)). The udi_devmgmt_ack(3udi) call acknowledges the completion of the unbind operation on the driver side.

Next topic: udi_cmos child channel operations
Previous topic: Channel operations

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