Many of the drivers in this framework are accessed through an asynchronous request API built around a common data structure: The
buffer. An asynchronous API is somewhat more complicated to use than a traditional synchronous API, where the function calls block until the request is done, but it offers several advantages:
- The CPU can do several things in parallel, improving performance.
- The CPU can be put to sleep when there's no work to be done, improving power consumption.
- Buffers can be passed around between all drivers supporting the generic buffer data structure with minimal overhead.
- The data transfered does not need to be in a single contiguous memory region.
The following sequence diagram describes the flow of a typical asynchronous request submitted by a Client to a Driver. When the request is being processed by hardware, the CPU may perform other tasks, or go to sleep if there's nothing to be done. The driver will call the callback associated with the request, if any, when the hardware signals that the request is complete.
Sometimes, the driver may need to perform multiple hardware operations in order to process a single request. This is completely hidden from the client. The driver may also keep a request queue, allowing multiple clients to submit any number of requests, which will often provide better utilization of the hardware.
While the definition of a request data structure may vary slightly from driver to driver, they all contain a linked list of
buffer structures.
A buffer represents a memory region which is contiguous in physical and virtual memory. A request can have multiple buffers, allowing transfers to and from discontiguous memory regions.
The address of the memory region associated with a
buffer is described using the type
buffer_addr_t. This can be either a struct or a union with two members:
- phys - The physical address of the memory region, used for DMA
- addr - The virtual address of the memory region, used for CPU access.
On some processors, for example the AVR32 UC3 family, the virtual address is always equal to the physical address, so the type is defined as a union to save space. Other processors, for example the AVR32 AP7 family, have integrated MMU hardware, so the software cannot assume that the virtual and physical addresses are identical. In this case, the type is defined as a struct in order to keep track of the addresses separately.
The size of the memory region in bytes is kept in the buffer::len field. This also indicates how many bytes that are to be transfered for one particular buffer segment.
The
buffer::dma_desc field is for use by drivers that support DMA transfers to keep track of any descriptors used by the DMA hardware. It may become optional on some platforms that don't have any DMA hardware.
The buffer::node field is a list node primarily used for keeping the buffer on the request's buffer list. Drivers are free to move the buffers onto other lists while the request is being processed, but the original list must be restored before the request is handed back to the client.
The asynchronous request objects are subsystem-specific, but they usually have several fields in common:
- buf_list - A linked list of buffers holding data associated with the request
- node - A linked list node allowing the request itself to be placed in a linked list.
- req_done - A callback called by the driver when the request is done or aborted.
- context - Arbitrary data for use by the client
- status - A status code indicating success or failure
- bytes_xfered - How many bytes that were actually transfered. This may be less than the sum of all the buffer lengths if the request was terminated early for some reason (for example, a short packet may terminate a USB request before all buffers have been filled.)