Skip to content

Commit

Permalink
drm/tegra: Implement correct DMA-BUF semantics
Browse files Browse the repository at this point in the history
DMA-BUF requires that each device that accesses a DMA-BUF attaches to it
separately. To do so the host1x_bo_pin() and host1x_bo_unpin() functions
need to be reimplemented so that they can return a mapping, which either
represents an attachment or a map of the driver's own GEM object.

Signed-off-by: Thierry Reding <treding@nvidia.com>
  • Loading branch information
Thierry Reding committed Dec 16, 2021
1 parent 7a56783 commit c6aeaf5
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 288 deletions.
163 changes: 102 additions & 61 deletions drivers/gpu/drm/tegra/gem.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,86 +23,96 @@

MODULE_IMPORT_NS(DMA_BUF);

static void tegra_bo_put(struct host1x_bo *bo)
static unsigned int sg_dma_count_chunks(struct scatterlist *sgl, unsigned int nents)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
dma_addr_t next = ~(dma_addr_t)0;
unsigned int count = 0, i;
struct scatterlist *s;

drm_gem_object_put(&obj->gem);
}
for_each_sg(sgl, s, nents, i) {
/* sg_dma_address(s) is only valid for entries that have sg_dma_len(s) != 0. */
if (!sg_dma_len(s))
continue;

/* XXX move this into lib/scatterlist.c? */
static int sg_alloc_table_from_sg(struct sg_table *sgt, struct scatterlist *sg,
unsigned int nents, gfp_t gfp_mask)
{
struct scatterlist *dst;
unsigned int i;
int err;
if (sg_dma_address(s) != next) {
next = sg_dma_address(s) + sg_dma_len(s);
count++;
}
}

err = sg_alloc_table(sgt, nents, gfp_mask);
if (err < 0)
return err;
return count;
}

dst = sgt->sgl;
static inline unsigned int sgt_dma_count_chunks(struct sg_table *sgt)
{
return sg_dma_count_chunks(sgt->sgl, sgt->nents);
}

for (i = 0; i < nents; i++) {
sg_set_page(dst, sg_page(sg), sg->length, 0);
dst = sg_next(dst);
sg = sg_next(sg);
}
static void tegra_bo_put(struct host1x_bo *bo)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);

return 0;
drm_gem_object_put(&obj->gem);
}

static struct sg_table *tegra_bo_pin(struct device *dev, struct host1x_bo *bo,
dma_addr_t *phys)
static struct host1x_bo_mapping *tegra_bo_pin(struct device *dev, struct host1x_bo *bo,
enum dma_data_direction direction)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
struct sg_table *sgt;
struct drm_gem_object *gem = &obj->gem;
struct host1x_bo_mapping *map;
int err;

map = kzalloc(sizeof(*map), GFP_KERNEL);
if (!map)
return ERR_PTR(-ENOMEM);

map->bo = host1x_bo_get(bo);
map->direction = direction;
map->dev = dev;

/*
* If we've manually mapped the buffer object through the IOMMU, make
* sure to return the IOVA address of our mapping.
*
* Similarly, for buffers that have been allocated by the DMA API the
* physical address can be used for devices that are not attached to
* an IOMMU. For these devices, callers must pass a valid pointer via
* the @phys argument.
*
* Imported buffers were also already mapped at import time, so the
* existing mapping can be reused.
* Imported buffers need special treatment to satisfy the semantics of DMA-BUF.
*/
if (phys) {
*phys = obj->iova;
return NULL;
if (gem->import_attach) {
struct dma_buf *buf = gem->import_attach->dmabuf;

map->attach = dma_buf_attach(buf, dev);
if (IS_ERR(map->attach)) {
err = PTR_ERR(map->attach);
goto free;
}

map->sgt = dma_buf_map_attachment(map->attach, direction);
if (IS_ERR(map->sgt)) {
dma_buf_detach(buf, map->attach);
err = PTR_ERR(map->sgt);
goto free;
}

err = sgt_dma_count_chunks(map->sgt);
map->size = gem->size;

goto out;
}

/*
* If we don't have a mapping for this buffer yet, return an SG table
* so that host1x can do the mapping for us via the DMA API.
*/
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
if (!sgt)
return ERR_PTR(-ENOMEM);
map->sgt = kzalloc(sizeof(*map->sgt), GFP_KERNEL);
if (!map->sgt) {
err = -ENOMEM;
goto free;
}

if (obj->pages) {
/*
* If the buffer object was allocated from the explicit IOMMU
* API code paths, construct an SG table from the pages.
*/
err = sg_alloc_table_from_pages(sgt, obj->pages, obj->num_pages,
0, obj->gem.size, GFP_KERNEL);
if (err < 0)
goto free;
} else if (obj->sgt) {
/*
* If the buffer object already has an SG table but no pages
* were allocated for it, it means the buffer was imported and
* the SG table needs to be copied to avoid overwriting any
* other potential users of the original SG table.
*/
err = sg_alloc_table_from_sg(sgt, obj->sgt->sgl,
obj->sgt->orig_nents, GFP_KERNEL);
err = sg_alloc_table_from_pages(map->sgt, obj->pages, obj->num_pages, 0, gem->size,
GFP_KERNEL);
if (err < 0)
goto free;
} else {
Expand All @@ -111,25 +121,56 @@ static struct sg_table *tegra_bo_pin(struct device *dev, struct host1x_bo *bo,
* not imported, it had to be allocated with the DMA API, so
* the DMA API helper can be used.
*/
err = dma_get_sgtable(dev, sgt, obj->vaddr, obj->iova,
obj->gem.size);
err = dma_get_sgtable(dev, map->sgt, obj->vaddr, obj->iova, gem->size);
if (err < 0)
goto free;
}

return sgt;
err = dma_map_sgtable(dev, map->sgt, direction, 0);
if (err)
goto free_sgt;

out:
/*
* If we've manually mapped the buffer object through the IOMMU, make sure to return the
* existing IOVA address of our mapping.
*/
if (!obj->mm) {
map->phys = sg_dma_address(map->sgt->sgl);
map->chunks = err;
} else {
map->phys = obj->iova;
map->chunks = 1;
}

map->size = gem->size;

return map;

free_sgt:
sg_free_table(map->sgt);
free:
kfree(sgt);
kfree(map->sgt);
kfree(map);
return ERR_PTR(err);
}

static void tegra_bo_unpin(struct device *dev, struct sg_table *sgt)
static void tegra_bo_unpin(struct host1x_bo_mapping *map)
{
if (sgt) {
sg_free_table(sgt);
kfree(sgt);
if (!map)
return;

if (map->attach) {
dma_buf_unmap_attachment(map->attach, map->sgt, map->direction);
dma_buf_detach(map->attach->dmabuf, map->attach);
} else {
dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0);
sg_free_table(map->sgt);
kfree(map->sgt);
}

host1x_bo_put(map->bo);
kfree(map);
}

static void *tegra_bo_mmap(struct host1x_bo *bo)
Expand Down
60 changes: 15 additions & 45 deletions drivers/gpu/drm/tegra/plane.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ tegra_plane_atomic_duplicate_state(struct drm_plane *plane)

for (i = 0; i < 3; i++) {
copy->iova[i] = DMA_MAPPING_ERROR;
copy->sgt[i] = NULL;
copy->map[i] = NULL;
}

return &copy->base;
Expand Down Expand Up @@ -138,55 +138,37 @@ const struct drm_plane_funcs tegra_plane_funcs = {

static int tegra_dc_pin(struct tegra_dc *dc, struct tegra_plane_state *state)
{
struct iommu_domain *domain = iommu_get_domain_for_dev(dc->dev);
unsigned int i;
int err;

for (i = 0; i < state->base.fb->format->num_planes; i++) {
struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i);
dma_addr_t phys_addr, *phys;
struct sg_table *sgt;
struct host1x_bo_mapping *map;

/*
* If we're not attached to a domain, we already stored the
* physical address when the buffer was allocated. If we're
* part of a group that's shared between all display
* controllers, we've also already mapped the framebuffer
* through the SMMU. In both cases we can short-circuit the
* code below and retrieve the stored IOV address.
*/
if (!domain || dc->client.group)
phys = &phys_addr;
else
phys = NULL;

sgt = host1x_bo_pin(dc->dev, &bo->base, phys);
if (IS_ERR(sgt)) {
err = PTR_ERR(sgt);
map = host1x_bo_pin(dc->dev, &bo->base, DMA_TO_DEVICE);
if (IS_ERR(map)) {
err = PTR_ERR(map);
goto unpin;
}

if (sgt) {
err = dma_map_sgtable(dc->dev, sgt, DMA_TO_DEVICE, 0);
if (err)
goto unpin;

if (!dc->client.group) {
/*
* The display controller needs contiguous memory, so
* fail if the buffer is discontiguous and we fail to
* map its SG table to a single contiguous chunk of
* I/O virtual memory.
*/
if (sgt->nents > 1) {
if (map->chunks > 1) {
err = -EINVAL;
goto unpin;
}

state->iova[i] = sg_dma_address(sgt->sgl);
state->sgt[i] = sgt;
state->iova[i] = map->phys;
} else {
state->iova[i] = phys_addr;
state->iova[i] = bo->iova;
}

state->map[i] = map;
}

return 0;
Expand All @@ -195,15 +177,9 @@ static int tegra_dc_pin(struct tegra_dc *dc, struct tegra_plane_state *state)
dev_err(dc->dev, "failed to map plane %u: %d\n", i, err);

while (i--) {
struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i);
struct sg_table *sgt = state->sgt[i];

if (sgt)
dma_unmap_sgtable(dc->dev, sgt, DMA_TO_DEVICE, 0);

host1x_bo_unpin(dc->dev, &bo->base, sgt);
host1x_bo_unpin(state->map[i]);
state->iova[i] = DMA_MAPPING_ERROR;
state->sgt[i] = NULL;
state->map[i] = NULL;
}

return err;
Expand All @@ -214,15 +190,9 @@ static void tegra_dc_unpin(struct tegra_dc *dc, struct tegra_plane_state *state)
unsigned int i;

for (i = 0; i < state->base.fb->format->num_planes; i++) {
struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i);
struct sg_table *sgt = state->sgt[i];

if (sgt)
dma_unmap_sgtable(dc->dev, sgt, DMA_TO_DEVICE, 0);

host1x_bo_unpin(dc->dev, &bo->base, sgt);
host1x_bo_unpin(state->map[i]);
state->iova[i] = DMA_MAPPING_ERROR;
state->sgt[i] = NULL;
state->map[i] = NULL;
}
}

Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/tegra/plane.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct tegra_plane_legacy_blending_state {
struct tegra_plane_state {
struct drm_plane_state base;

struct sg_table *sgt[3];
struct host1x_bo_mapping *map[3];
dma_addr_t iova[3];

struct tegra_bo_tiling tiling;
Expand Down
Loading

0 comments on commit c6aeaf5

Please sign in to comment.