|
DMA buffer sharing in 3.3
ByJonathan Corbet January 11, 2012
Back in August 2011, LWN looked at the DMA
buffer sharing patch set posted by Marek Szyprowski. Since then, that
patch has been picked up by Sumit Semwal, who modified it considerably in
response to comments from a number of developers. The version of this
patch that was merged for 3.3 differs enough from its predecessors that it
merits another look here.
The core idea remains the same, though: this mechanism allows DMA buffers
to be shared between drivers that might otherwise be unaware of each
other. The initial target use is sharing buffers between producers and
consumers of video streams; a camera device, for example, could acquire a
stream of frames into a series of buffers that are shared with the graphics
adapter, enabling the capture and display of the data with no copying in
the kernel.
In the 3.3 sharing scheme, one driver will set itself up as an exporter of
sharable buffers. That requires providing a set of callbacks to the buffer
sharing code:
struct dma_buf_ops {
int (*attach)(struct dma_buf *buf, struct device *dev,
struct dma_buf_attachment *dma_attach);
void (*detach)(struct dma_buf *buf, struct dma_buf_attachment *dma_attach);
struct sg_table *(*map_dma_buf)(struct dma_buf_attachment *dma_attach,
enum dma_data_direction dir);
void (*unmap_dma_buf)(struct dma_buf_attachment *dma_attach, struct sg_table *sg);
void (*release)(struct dma_buf *);
};
Briefly, attach() and detach() inform the exporting
driver when others take or release references to the buffer. The
map_dma_buf() and unmap_dma_buf() callbacks, instead,
cause the buffer to be prepared (or unprepared) for DMA and pass ownership
between drivers. A call to release() will be made when the last
reference to the buffer is released.
The exporting driver makes the buffer available with a call to:
struct dma_buf *dma_buf_export(void *priv, struct dma_buf_ops *ops,
size_t size, int flags);
Note that the size of the buffer is specified here, but there is
no pointer to the buffer itself. In fact, the current version of the
interface never passes around CPU-accessible buffer pointers at all.
One of the actions performed by dma_buf_export() is the creation
of an anonymous file to represent the buffer; flags is used to set
the mode bits on that file.
Since the file is anonymous, it is not visible to the rest of the kernel
(or user space) in any useful way. Truly exporting the buffer, instead,
requires obtaining a file descriptor for it and making that descriptor
available to user space. The descriptor can be had with:
int dma_buf_fd(struct dma_buf *dmabuf);
There is no standardized mechanism for passing that file descriptor to user
space, so it seems likely that any subsystem implementing this
functionality will add its own special ioctl() operation to get a
buffer's file descriptor. The same is true for the act of passing a file
descriptor to drivers that will share this buffer; it is something that
will happen outside of the buffer-sharing API.
A driver wishing to share a DMA buffer has to go through a series of calls
after obtaining the corresponding file descriptor, the first of which is:
struct dma_buf *dma_buf_get(int fd);
This function obtains a reference to the buffer and returns a
dma_buf structure pointer that can be used with the other API calls to
refer to the buffer. When the driver is finished with the buffer, it
should be returned with a call to dma_buf_put().
The next step is to "attach" to the buffer with:
struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf,
struct device *dev);
This function will allocate and fill in yet another structure:
struct dma_buf_attachment {
struct dma_buf *dmabuf;
struct device *dev;
struct list_head node;
void *priv;
};
That structure will then be passed to the exporting driver's
attach() callback. There seems to be a couple of reasons for the
existence of this step, the first of which is simply to let the exporting
driver know about the consumers of the buffer. Beyond that, the
device structure passed by the calling driver can contain a
pointer (in its dma_params field) to one of these structures:
struct device_dma_parameters {
unsigned int max_segment_size;
unsigned long segment_boundary_mask;
};
The exporting driver should look at these constraints and ensure that the
buffer it is exporting can satisfy them; if not, the attach() call
should fail. If multiple drivers attach to the buffer, the exporting
driver will need to allocate the buffer in a way that satisfies all of
their constraints.
The final step is to map the buffer for DMA:
struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach,
enum dma_data_direction direction);
This call turns into a call to the exporting driver's
map_dma_buf() callback.
If this call succeeds, the return value will be a scatterlist that can be
used to program the DMA operation into the device. A successful return
also means that the calling driver's device owns the buffer; it should not
be touched by the CPU during this time.
Note that mapping a buffer is an operation
that can block for a number of reasons; if the buffer is busy elsewhere,
for example.
Also worth noting is that, until this call is made, the buffer need not
necessarily be allocated anywhere. The exporting driver can wait until
others have attached to the buffer so that it can see their DMA constraints
and allocate the buffer accordingly. Of course, if the buffer lives in
device memory or is otherwise constrained on the exporting side, it can be
allocated sooner.
After the DMA operation is completed, the sharing driver should unmap the
buffer with:
void dma_buf_unmap_attachment(struct dma_buf_attachment *attach,
struct sg_table *sg_table);
That will, in turn, generate a call to the exporting driver's
unmap_dma_buf() function. Detaching from the buffer (when it is
no longer needed) can be done with:
void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach);
As might be expected, this function will call the exporting driver's
detach() callback.
As of 3.3, there are no users for this interface in the mainline kernel.
There seems to be a fair amount of interest in using it, though, so Dave
Airlie pushed it into the mainline with the
idea that it would make the development of users easier. Some of those
users can be seen (in an early form) in Dave's
drm-prime repository and Rob
Clark's OMAP4 tree.
(Log in to post comments)
|