blob: 7cdf19d898697af9ceb72f7c3012f0e242306e18 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Support for using dma-bufs.
*
* Copyright (C) 2022 Google LLC
*/
#include <linux/dma-buf.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/version.h>
#include "gxp-dma.h"
#include "gxp-dmabuf.h"
#include "gxp-vd.h"
struct gxp_dmabuf_mapping {
struct gxp_mapping mapping;
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
/*
* For normal mappings, the `sg_table` is embedded directly in the
* `gxp_mapping` and populated by `sg_alloc_table_from_pages()`.
* For dma-bufs however, a pointer to the `sg_table` is returned by
* `dma_buf_map_attachment()`.
*
* Rather than manage the memory of `gxp_mapping`'s `sg_table`
* independently so it can contain a pointer, dma-bufs store their
* `sg_table` pointer here and ignore `mapping->sgt`.
*/
struct sg_table *sgt;
};
/* Mapping destructor for gxp_mapping_put() to call */
static void destroy_dmabuf_mapping(struct gxp_mapping *mapping)
{
struct gxp_dmabuf_mapping *dmabuf_mapping;
struct gxp_dev *gxp = mapping->gxp;
/* Unmap and detach the dma-buf */
dmabuf_mapping =
container_of(mapping, struct gxp_dmabuf_mapping, mapping);
gxp_dma_unmap_dmabuf_attachment(gxp, mapping->domain,
dmabuf_mapping->attachment,
dmabuf_mapping->sgt, mapping->dir);
dma_buf_detach(dmabuf_mapping->dmabuf, dmabuf_mapping->attachment);
dma_buf_put(dmabuf_mapping->dmabuf);
kfree(dmabuf_mapping);
}
struct gxp_mapping *gxp_dmabuf_map(struct gxp_dev *gxp,
struct gcip_iommu_domain *domain, int fd,
u32 flags, enum dma_data_direction dir)
{
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *sgt;
struct gxp_dmabuf_mapping *dmabuf_mapping;
int ret = 0;
if (!valid_dma_direction(dir))
return ERR_PTR(-EINVAL);
dmabuf = dma_buf_get(fd);
if (IS_ERR(dmabuf)) {
dev_err(gxp->dev, "Failed to get dma-buf to map (ret=%ld)\n",
PTR_ERR(dmabuf));
return ERR_CAST(dmabuf);
}
attachment = dma_buf_attach(dmabuf, gxp->dev);
if (IS_ERR(attachment)) {
dev_err(gxp->dev, "Failed to attach dma-buf to map (ret=%ld)\n",
PTR_ERR(attachment));
ret = PTR_ERR(attachment);
goto err_attach;
}
sgt = gxp_dma_map_dmabuf_attachment(gxp, domain, attachment, flags, dir);
if (IS_ERR(sgt)) {
dev_err(gxp->dev,
"Failed to map dma-buf attachment (ret=%ld)\n",
PTR_ERR(sgt));
ret = PTR_ERR(sgt);
goto err_map_attachment;
}
dmabuf_mapping = kzalloc(sizeof(*dmabuf_mapping), GFP_KERNEL);
if (!dmabuf_mapping) {
ret = -ENOMEM;
goto err_alloc_mapping;
}
/* dma-buf mappings are indicated by a host_address of 0 */
refcount_set(&dmabuf_mapping->mapping.refcount, 1);
dmabuf_mapping->mapping.destructor = destroy_dmabuf_mapping;
dmabuf_mapping->mapping.host_address = 0;
dmabuf_mapping->mapping.gxp = gxp;
dmabuf_mapping->mapping.domain = domain;
dmabuf_mapping->mapping.device_address = sg_dma_address(sgt->sgl);
dmabuf_mapping->mapping.dir = dir;
dmabuf_mapping->mapping.size = dmabuf->size;
dmabuf_mapping->dmabuf = dmabuf;
dmabuf_mapping->attachment = attachment;
dmabuf_mapping->sgt = sgt;
return &dmabuf_mapping->mapping;
err_alloc_mapping:
gxp_dma_unmap_dmabuf_attachment(gxp, domain, attachment, sgt, dir);
err_map_attachment:
dma_buf_detach(dmabuf, attachment);
err_attach:
dma_buf_put(dmabuf);
return ERR_PTR(ret);
}
struct sg_table *gxp_dmabuf_get_sgt(struct gxp_mapping *mapping)
{
struct gxp_dmabuf_mapping *dmabuf_mapping;
if (mapping->host_address)
/* Not a dmabuf */
return NULL;
dmabuf_mapping = container_of(mapping, struct gxp_dmabuf_mapping, mapping);
return dmabuf_mapping->sgt;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)
MODULE_IMPORT_NS(DMA_BUF);
#endif