|
|
5.1 Overview
Data available to a UDI driver can be categorized as (1) module-global data, (2) per-instance data, (3) per-request data, or (4) function-local variables.
Module-global data has driver-scope; i.e., it is global to all instances of a driver within a given domain, and for that reason is sometimes called domain-global data. All non-automatic variables in the driver, whether local to particular functions or compilation units, or truly global to the driver, are considered module-global data. Module-global data is read-only throughout the execution of a UDI driver, regardless of whether or not it is declared with the ISO C "const" keyword. Environment implementations may choose to share a single copy of a driver's module-global constant data between multiple instances of that driver within a particular domain.
Per-instance data has region-scope and is often referred to as region-local data or simply region data. A driver instance is composed of one or more regions, each of which has its own private data which isn't visible to or shareable with other regions. The region data model allows drivers to be instance independent, meaning that the driver state for each device instance is independent of all other instances so that a new instance can be added at any time or an instance can be removed and the remaining instances will continue independently. It is also critical that, when a driver is entered on behalf of a particular device instance, it does not access any hardware of another device instance; this allows the driver instances to be independently bound to different CPU's (or, on ccNUMA configurations, CPU groups) or otherwise constrained to specific locations.
Any data objects allocated by the driver when executing within a driver region are attached to the region and are region-local.
Data that is not specific to a particular channel or individual operation is sometimes referred to as region-global data, since it is global to the region.
Data objects such as control blocks passed into the region from another region contain per-request data. The ownership of these objects is transferred to the target region and they therefore also become region-local to the target region and no longer accessible from the source region. Objects which can be transferred from one region to another are called transferable.
Function-local variables are C variables of function or block scope. C global variables (i.e., C variables defined outside of any function) may only be used for module-global data, and therefore must be read-only. It is recommended that all such variables be declared as static constants using the C language const and static keywords.
While executing in a region, module-global data space (including static variables with function scope as well as global variables) is read-only, but dynamically allocated region data is read-write.
5.2 Data Objects
In general, the term UDI data objects refers to allocated data objects which are obtained via a call to a UDI allocation interface. UDI data objects include driver-addressable memory areas, metalanguage control blocks, and opaque objects referenced via handles. UDI data objects have the following properties associated with them: scope, transferability, and opaqueness. The scope can either be module-global or region-local as described above. Secondly, region-local objects can either be transferable or non-transferable as described previously. Thirdly, UDI data objects can be visible, semi-opaque, or opaque. Allocated driver structures are visible; control blocks are semi-opaque; handles reference opaque objects. Visible and semi-opaque objects are both referenced by pointers; however, semi-opaque objects are defined such that the environment may-and probably will-store additional data, which is not available to the driver, before or after the driver-visible fields of the object.
5.2.1 Memory Objects
Blocks of driver-addressable memory may be allocated by the driver at any time, using udi_mem_alloc. Most allocated memory is private to the region that allocated it and cannot be transferred to other regions. However, drivers may also allocate movable memory blocks, which can be passed as arguments to channel operations and thus transferred to other regions. Once a movable memory block is "given away", however, the original driver must no longer access it. Only one region at a time "owns" a movable memory block. Movable memory is allocated using udi_mem_alloc with the UDI_MEM_MOVABLE flag.
5.2.1.1 Using Memory Pointers with Asynchronous Service Calls
Some asynchronous service calls take pointers to driver memory objects as parameters. Since the environment might continue to access these objects after returning to the calling code in the driver (any time until the environment completes the service call by calling the driver's callback routine), special care must be taken to avoid race conditions and corruptions that might happen if both the environment and driver were using the memory at the same time.
To avoid the possibility of such race conditions, UDI requires drivers to obey the following rules for all memory object pointers passed as explicit parameters to asynchronous service calls. These rules do not apply to parameters that expect specific types of semi-opaque objects such as control blocks or UDI buffers.
The memory pointed to by such pointer parameters (if non-NULL) must be either movable memory, part of the control block's scratch space, or part of a module-global (and thus, read-only) variable. (See Section 5.2.2 for more details on control blocks and scratch space.) In particular, memory allocated on the stack in local variables must not be passed to asynchronous service calls because the stack frame may no longer exist when the pointer is finally dereferenced. If movable memory is used, the pointer must point to the beginning of the movable memory block and the driver must not read, write, or pass to other environment service calls or channel operations any portion of the movable memory block until the completion callback has been called.
For some service calls, pointer parameter values may be NULL. See the definition of each service call to determine whether or not it accepts NULL pointers.
5.2.2 Control Blocks
A control block is a structure used within UDI to represent an asynchronous request to or from the driver. Control blocks provide the context and associated data to describe each request. All region-context entry points into a driver are called with an associated control block.
Control blocks are used for all metalanguage channel operations and asynchronous service calls. Each time a channel operation is performed, the requesting driver region passes a control block specific to the request; the receiving region receives that control block and uses it to maintain the context for the request, typically returning the control block to the requesting region via an acknowledgment operation once the associated task has been completed. Likewise, when a driver makes a UDI service call that may not complete immediately it provides a control block that will be passed back to the driver in the callback operation to provide the context for that call.
5.2.2.1 Scratch Space
Each control block contains additional space that may be used by the driver to store information related to a request. This space is referred to as the scratch space of the control block and its contents are determined by the driver. Scratch space is accessed via a scratch pointer in the control block.
Scratch space contents will be preserved across asynchronous service calls (see Section 4.8.1.2) and the driver's callback will always be invoked with the same control block that was originally passed to the asynchronous service call.
When the current operation is completed by transferring the control block to another region, ownership of the corresponding scratch space is also relinquished and the contents will not be preserved. The driver should not expect to receive the same control block back for any future operations, nor should it expect the same scratch space or scratch space contents to be maintained for that control block.
Drivers specify their scratch space requirements through the udi_cb_init_t structure as part of the udi_init_info initialization information (see udi_cb_init_t). The scratch space for a control block may actually change size (invisibly to the driver) as it is passed from region to region and is adjusted to meet the requirements of the receiving region. If the driver's scratch requirement is zero, the value of the scratch pointer is unspecified and it must not be dereferenced.
5.2.2.2 Inline Data
Some control block types have inline data elements associated with them. These are blocks of memory pointed to by fields within the visible portion of the control block that are automatically allocated when the control block is allocated. Drivers specify the size and, in some cases, the structure of inline elements through additional fields in the udi_cb_init_t structure. Inline memory pointers in control block structures are initialized by the environment and must not be modified by drivers.
5.2.2.3 Control Block Groups
Each metalanguage defines the format and contents of the various control blocks used for the interface operations used in that metalanguage. As part of this definition, the metalanguage organizes these control blocks into one or more control block groups, each with a corresponding control block group number. The control block group defines the allocation granularity for control blocks; the udi_cb_alloc operation (see udi_cb_alloc) is passed a control block index which the driver has correlated to the metalanguage's defined control block group number by including them in a udi_cb_init_t structure (see udi_cb_init_t). Thus, when a control block is allocated the result can be used as any of the control block types defined for that group (using appropriate type casts); the specific type is determined by the driver's initialization of that control block and the subsequent channel operation to which that control block is passed.
By using control block groups a metalanguage can reduce the overall cost of managing control block types for that metalanguage; frequently there are only a few control block groups defined within a metalanguage whereas there may be a large number of individual control block types to match the channel interface operations.
5.2.2.4 Control Block Synchronization
It is important to note that using a control block for a service call does not transfer ownership of that control block to another region. The driver must not use any part of the control block, including the scratch space, for other activities (i.e. passing it to another asynchronous service call or channel operation, or accessing any of its contents) until returned via the callback routine, but the contents of the control block's visible portion and scratch space are preserved and unmodified by the service call. In this way the driver may maintain its internal context for a request across an asynchronous service call.
5.2.2.5 Control Block Recycling
Channel operations are typically defined in pairs. For each type of initiating request or indication operation there are one or more corresponding response operation types. When a driver receives a control block as part of a request or indication operation, it must use the same control block in the response operation. The initiating driver may then free the control block, but if it expects to initiate additional operations of the same type it should instead maintain a pool of control blocks which it reuses for subsequent operations.
When a driver forwards a request, in some form, to another region, it must use a new control block for the layered request, rather than attempting to forward the original control block directly. This ensures that the original initiator's context is preserved.
5.2.2.6 Control Block Pointer Invariance
The pointer value used to identify a control block and to access its visible fields remains valid as long as the control block is owned by the same region, even across asynchronous service calls and callbacks. Once a control block is given away via a channel operation, however, the pointer value is valid only for purposes of aborting outstanding operations.
When a control block is returned to the initiating region as part of a response operation, the pointer value may or may not be the same as the original control block pointer, even though it refers to the same underlying control block. The environment may choose to reallocate and/or re-map the memory for the control block when it passes between regions.
5.2.3 Region Data
Each region created for the driver will have an associated block of memory referred to as region data. This region data is a per-instance region of memory that is only accessible by the associated region and is used by that region to store information relevant to the operation of that region. This region-specific information often includes: state variables, request queues, PIO handles for accessing the device, and information about the channels connected to that region.
When a region is created the initial contents of the start of the region data area are initialized to be a udi_init_context_t structure (see udi_init_context_t). The driver may choose to preserve this structure or overwrite it; once the region data has been created and initialized in this manner its subsequent use and contents are determined entirely by the associated driver region code.
5.3 Channel Context
A channel context is a driver-defined context value that is associated with a specific channel. The channel context is a single pointer value and is typically used to point to the region-global data structure for the region associated with that channel or to a more specific structure which in turn points to the region-global data. The channel context is the only information provided to the target region for a channel operation beyond the operation-specific data in the control block and associated parameters.
On entry to a region via a channel operation, the control block's context pointer is set to the channel context for the channel over which the operation was received.
5.4 Transferable Objects
Most of the data objects provided to or allocated by a region are not transferable to other regions. This allows the management and handling of those data objects to be optimized.
Specific data objects may be identified as transferable (or allocated that way as in the case of the udi_mem_alloc operation). When an object is transferable, there are further considerations which the driver and the environment must make in using and transferring those objects, including remapping or copying of parameters and associated data when the transfer crosses a domain boundary as well as various constraints and alignment issues for the memory associated with the transferable object.
In addition, once the driver has transferred an object to another region it may no longer use or reference that object, even if it still has a local pointer or variable reference to that object. Only one region may "own" a transferable object at any one time.
5.5 Implicit MP Synchronization
As indicated above, a region consists of a set of allocated data private to that region and a current thread of execution (unless it is idle). At most one thread may be active in any given region at one time; once a driver region is entered, all other attempts to enter the region will be deferred until after the first call returns. This deferral may be achieved through spin-waiting (on another CPU), queueing, or other implementation-specific methods.
Three factors in the UDI execution and data models combine to achieve implicit MP synchronization. These are:
- All region data accessible to the driver is private to the region and may not be accessed from other regions or other entry point routines.
- All module-global data is read-only.
- Only one thread may be executing in a region at one time.
This guarantees that all data accesses within a UDI driver are single-threaded, so no explicit locking primitives are needed to run the driver in a Multi-Processor environment.
At the same time, a UDI driver can still take advantage of MP parallelism, since multiple driver instances can run in parallel and the entire driver can run in parallel with other drivers and other system activity. A driver may also increase its parallelism by using additional regions per driver instance (secondary regions) and dividing the work into mutually parallel pieces.