DMA Transfers (Asynchronous)
Character drivers supporting aread(9E) and awrite(9E) use aphysio(9F) instead of physio(9F).
int aphysio(int (*strat)(struct buf *), int (*cancel)(struct buf *), dev_t dev, int rw, void (*mincnt)(struct buf *), struct aio_req *aio_reqp); |
Note - The address of anocancel(9F) is the only value that can currently be passed as the second argument to aphysio(9F).
aphysio(9F) requires the driver to pass the address of a strategy(9E) routine. aphysio(9F) ensures that memory space is locked down (cannot be paged out) for the duration of the data transfer. This is necessary for DMA transfers because they cannot handle page faults. aphysio(9F) also provides an automated way of breaking a larger transfer into a series of smaller, more manageable ones. See "minphys() Entry Point" for more information. Example 10-5 and Example 10-6 demonstrate that the aread(9E) and awrite(9E) entry points differ only slightly from the read(9E) and write(9E) entry points; the difference lies mainly in their use of aphysio(9F) instead of physio(9F).
Example 10-6 aread(9E) and awrite(9E) Routines Using aphysio(9F)
In the call to aphysio(9F), xxstrategy() is a pointer to the driver strategy routine. aiop is a pointer to the aio_req(9S) structure and is also passed to aread(9E) and awrite(9E). aio_req(9S) describes where the data is to be stored in user space. aphysio(9F) returns zero if the I/O request is scheduled successfully or an error number on failure. After calling strategy(9E), aphysio(9F) returns without waiting for the I/O to complete or fail.
minphys() Entry Point
xxminphys() is a pointer to a function to be called by physio(9F) or aphysio(9F) to ensure that the size of the requested transfer does not exceed a driver-imposed limit. If the user requests a larger transfer, strategy(9E) will be called repeatedly, requesting no more than the imposed limit at a time. This is important because DMA resources are limited. Drivers for slow devices, such as printers, should be careful not to tie up resources for a long time.
Usually, a driver passes the address of the kernel function minphys(9F), but the driver can define its own xxminphys() routine instead. The job of xxminphys() is to keep the b_bcount field of the buf(9S) structure below a driver limit. There might be additional system limits that the driver should not circumvent, so the driver xxminphys() routine should call the system minphys(9F) routine after setting the b_bcount field and before returning.
Example 10-7 minphys(9F) Routine
#define XXMINVAL (512 << 10) /* 512 KB */ static void xxminphys(struct buf *bp) { if (bp->b_bcount > XXMINVAL) bp->b_bcount = XXMINVAL minphys(bp); } |
strategy() Entry Point
The strategy(9E) routine originated in block drivers and is so called because it can implement a strategy for efficient queuing of I/O requests to a block device. A driver for a character-oriented device can also use a strategy(9E) routine. In the character I/O model presented here, strategy(9E) does not maintain a queue of requests, but rather services one request at a time.
In Example 10-8, the strategy(9E) routine for a character-oriented DMA device allocates DMA resources for synchronous data transfer and starts the command by programming the device register (see Chapter 8, Direct Memory Access (DMA) for a detailed description).
Note - strategy(9E) does not receive a device number (dev_t) as a parameter; instead, this is retrieved from the b_edev field of the buf(9S) structure passed to strategy(9E).
Example 10-8 strategy(9E) Routine
Note - Although strategy(9E) is declared to return an int, it must always return zero.
On completion of the DMA transfer, the device generates an interrupt, causing the interrupt routine to be called. In Example 10-9, xxintr() receives a pointer to the state structure for the device that might have generated the interrupt.
Example 10-9 Interrupt Routine
The driver indicates an error by calling bioerror(9F). The driver must call biodone(9F) when the transfer is complete or after indicating an error with bioerror(9F).
Mapping Device Memory
Some devices, such as frame buffers, have memory that is directly accessible to user threads by way of memory mapping. Drivers for these devices typically do not support the read(9E) and write(9E) interfaces. Instead, these drivers support memory mapping with the devmap(9E) entry point. A typical example is a frame buffer driver that implements the devmap(9E) entry point to allow the frame buffer to be mapped in a user thread.
segmap() Entry Point
int xxsegmap(dev_t dev, off_t off, struct as *asp, caddr_t *addrp, off_t len, unsigned int prot, unsigned int maxprot, unsigned int flags, cred_t *credp); |
segmap(9E) is the entry point responsible for actually setting up a memory mapping requested by the system on behalf of an mmap(2) system call. Drivers for many memory-mapped devices will use ddi_devmap_segmap(9F) as the entry point rather than defining their own segmap(9E) routine.
If a driver wants to check mapping permissions or allocate private mapping resources before setting up the mapping, the driver can provide its own segmap(9E) entry point. segmap(9E) must call devmap_setup(9F) before returning.
In Example 10-10, the driver controls a frame buffer that allows write-only mappings. The driver returns EINVAL if the application tries to gain read access and then calls devmap_setup(9F) to set up the user mapping.
Example 10-10 segmap(9E) Routine