diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 85747365..a983c02b 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -766,6 +766,8 @@ static const struct drm_ioctl_desc tegra_drm_ioctls[] = { DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(TEGRA_SYNCPOINT_WAIT, tegra_drm_ioctl_syncpoint_wait, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(TEGRA_SYNCPOINT_EXPORT_MEMORY, tegra_drm_ioctl_syncpoint_export_memory, + DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(TEGRA_GEM_CREATE, tegra_gem_create, DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(TEGRA_GEM_MMAP, tegra_gem_mmap, DRM_RENDER_ALLOW), diff --git a/drivers/gpu/drm/tegra/include/uapi/drm/tegra_drm_next.h b/drivers/gpu/drm/tegra/include/uapi/drm/tegra_drm_next.h index d2a40c9f..e2637765 100644 --- a/drivers/gpu/drm/tegra/include/uapi/drm/tegra_drm_next.h +++ b/drivers/gpu/drm/tegra/include/uapi/drm/tegra_drm_next.h @@ -1066,6 +1066,47 @@ struct drm_tegra_syncpoint_wait { __u64 timestamp; }; +#define DRM_TEGRA_SYNCPOINT_EXPORT_MEMORY_READWRITE (1<<0) + +struct drm_tegra_syncpoint_export_memory { + /** + * @start: [in] + * + * ID of first syncpoint in exported memory region. + */ + __u32 start; + + /** + * @length: [in, out] + * + * Number of syncpoints in exported memory region. Specify 0 for all + * syncpoints from @start until end of syncpoint memory. The number + * of syncpoints in the returned buffer will be written to this field. + */ + __u32 length; + + /** + * @flags: [in] + * + * Flags. + */ + __u32 flags; + + /** + * @fd: [out] + * + * Dma-buf file descriptor corresponding to exported memory region. + */ + __s32 fd; + + /** + * @stride: [out] + * + * Stride in bytes between syncpoints in the exported memory region. + */ + __u32 stride; +}; + #define DRM_IOCTL_TEGRA_CHANNEL_OPEN DRM_IOWR(DRM_COMMAND_BASE + 0x10, struct drm_tegra_channel_open) #define DRM_IOCTL_TEGRA_CHANNEL_CLOSE DRM_IOWR(DRM_COMMAND_BASE + 0x11, struct drm_tegra_channel_close) #define DRM_IOCTL_TEGRA_CHANNEL_MAP DRM_IOWR(DRM_COMMAND_BASE + 0x12, struct drm_tegra_channel_map) @@ -1075,6 +1116,7 @@ struct drm_tegra_syncpoint_wait { #define DRM_IOCTL_TEGRA_SYNCPOINT_ALLOCATE DRM_IOWR(DRM_COMMAND_BASE + 0x20, struct drm_tegra_syncpoint_allocate) #define DRM_IOCTL_TEGRA_SYNCPOINT_FREE DRM_IOWR(DRM_COMMAND_BASE + 0x21, struct drm_tegra_syncpoint_free) #define DRM_IOCTL_TEGRA_SYNCPOINT_WAIT DRM_IOWR(DRM_COMMAND_BASE + 0x22, struct drm_tegra_syncpoint_wait) +#define DRM_IOCTL_TEGRA_SYNCPOINT_EXPORT_MEMORY DRM_IOWR(DRM_COMMAND_BASE + 0x24, struct drm_tegra_syncpoint_export_memory) #if defined(__cplusplus) } diff --git a/drivers/gpu/drm/tegra/uapi.c b/drivers/gpu/drm/tegra/uapi.c index 0f9342bc..70de9bd3 100644 --- a/drivers/gpu/drm/tegra/uapi.c +++ b/drivers/gpu/drm/tegra/uapi.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2020 NVIDIA Corporation */ +#include #include #include #include @@ -378,3 +379,186 @@ int tegra_drm_ioctl_syncpoint_wait(struct drm_device *drm, void *data, struct dr return 0; } + +struct tegra_drm_syncpoint_memory_data { + phys_addr_t base; + u32 start, length, stride; + bool readwrite; + struct host1x *host1x; +}; + +static struct sg_table *tegra_drm_syncpoint_memory_map_dma_buf( + struct dma_buf_attachment *attachment, enum dma_data_direction direction) +{ + struct tegra_drm_syncpoint_memory_data *priv = attachment->dmabuf->priv; + phys_addr_t mem_start = priv->base + priv->stride * priv->start; + size_t mem_length = priv->stride * priv->length; + dma_addr_t mem_start_dma; + struct sg_table *sgt; + int err; + + if (!priv->readwrite && direction != DMA_TO_DEVICE) + return ERR_PTR(-EPERM); + + if (!PAGE_ALIGNED(mem_start) || !PAGE_ALIGNED(mem_start + mem_length)) { + dev_err(attachment->dev, "denied mapping for unaligned syncpoint shim mapping\n"); + return ERR_PTR(-EINVAL); + } + + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) + return ERR_PTR(-ENOMEM); + + err = sg_alloc_table(sgt, 1, GFP_KERNEL); + if (err) + goto free_sgt; + + mem_start_dma = dma_map_resource(attachment->dev, mem_start, mem_length, direction, + DMA_ATTR_SKIP_CPU_SYNC); + err = dma_mapping_error(attachment->dev, mem_start_dma); + if (!mem_start_dma || err) + goto free_table; + + sg_set_page(sgt->sgl, phys_to_page(mem_start), mem_length, 0); + sg_dma_address(sgt->sgl) = mem_start_dma; + sg_dma_len(sgt->sgl) = mem_length; + + return sgt; + +free_table: + sg_free_table(sgt); +free_sgt: + kfree(sgt); + + return ERR_PTR(err); +} + +static void tegra_drm_syncpoint_memory_unmap_dma_buf( + struct dma_buf_attachment *attachment, struct sg_table *sgt, + enum dma_data_direction direction) +{ + dma_unmap_resource(attachment->dev, sg_dma_address(sgt->sgl), sg_dma_len(sgt->sgl), + direction, DMA_ATTR_SKIP_CPU_SYNC); + sg_free_table(sgt); + kfree(sgt); +} + +static void tegra_drm_syncpoint_memory_release(struct dma_buf *dma_buf) +{ + struct tegra_drm_syncpoint_memory_data *priv = dma_buf->priv; + int i; + + if (priv->readwrite) { + for (i = priv->start; i < priv->start + priv->length; i++) { + struct host1x_syncpt *sp = host1x_syncpt_get_by_id_noref(priv->host1x, i); + + host1x_syncpt_put(sp); + } + } + + kfree(priv); +} + +static const struct dma_buf_ops syncpoint_dmabuf_ops = { + .map_dma_buf = tegra_drm_syncpoint_memory_map_dma_buf, + .unmap_dma_buf = tegra_drm_syncpoint_memory_unmap_dma_buf, + .release = tegra_drm_syncpoint_memory_release, +}; + +int tegra_drm_ioctl_syncpoint_export_memory(struct drm_device *drm, void *data, + struct drm_file *file) +{ + struct host1x *host1x = tegra_drm_to_host1x(drm->dev_private); + struct drm_tegra_syncpoint_export_memory *args = data; + struct tegra_drm_file *fpriv = file->driver_priv; + struct tegra_drm_syncpoint_memory_data *priv; + u32 stride, num_syncpts, end_syncpts_user; + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct dma_buf *dma_buf; + phys_addr_t base; + int err, i; + + if (args->flags & ~DRM_TEGRA_SYNCPOINT_EXPORT_MEMORY_READWRITE) + return -EINVAL; + + err = host1x_syncpt_get_shim_info(host1x, &base, &stride, &num_syncpts); + if (err) + return err; + + if (check_add_overflow(args->start, args->length, &end_syncpts_user)) + return -EINVAL; + + if (end_syncpts_user >= num_syncpts) + return -EINVAL; + + if (args->length == 0) + args->length = num_syncpts - end_syncpts_user; + + if (args->flags & DRM_TEGRA_SYNCPOINT_EXPORT_MEMORY_READWRITE) { + mutex_lock(&fpriv->lock); + + for (i = args->start; i < args->start + args->length; i++) { + struct host1x_syncpt *sp = xa_load(&fpriv->syncpoints, i); + + if (!sp) { + mutex_unlock(&fpriv->lock); + return -EINVAL; + } + + host1x_syncpt_get(sp); + } + + mutex_unlock(&fpriv->lock); + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + err = -ENOMEM; + goto put_syncpts; + } + + priv->base = base; + priv->start = args->start; + priv->length = args->length; + priv->stride = stride; + priv->readwrite = (args->flags & DRM_TEGRA_SYNCPOINT_EXPORT_MEMORY_READWRITE); + priv->host1x = host1x; + + exp_info.ops = &syncpoint_dmabuf_ops; + exp_info.size = args->length * stride; + exp_info.flags = O_RDWR; + exp_info.priv = priv; + + dma_buf = dma_buf_export(&exp_info); + if (IS_ERR(dma_buf)) { + err = PTR_ERR(dma_buf); + goto free_priv; + } + + args->fd = dma_buf_fd(dma_buf, O_RDWR); + if (args->fd < 0) { + err = args->fd; + goto put_dma_buf; + } + + args->stride = stride; + + return 0; + +put_dma_buf: + dma_buf_put(dma_buf); + +free_priv: + kfree(priv); + +put_syncpts: + if (args->flags & DRM_TEGRA_SYNCPOINT_EXPORT_MEMORY_READWRITE) { + for (i = args->start; i < args->start + args->length; i++) { + struct host1x_syncpt *sp = host1x_syncpt_get_by_id_noref(host1x, i); + + host1x_syncpt_put(sp); + } + } + + return err; +} diff --git a/drivers/gpu/drm/tegra/uapi.h b/drivers/gpu/drm/tegra/uapi.h index 7703ebb3..3c8b24ae 100644 --- a/drivers/gpu/drm/tegra/uapi.h +++ b/drivers/gpu/drm/tegra/uapi.h @@ -51,6 +51,8 @@ int tegra_drm_ioctl_syncpoint_free(struct drm_device *drm, void *data, struct drm_file *file); int tegra_drm_ioctl_syncpoint_wait(struct drm_device *drm, void *data, struct drm_file *file); +int tegra_drm_ioctl_syncpoint_export_memory(struct drm_device *drm, void *data, + struct drm_file *file); void tegra_drm_uapi_close_file(struct tegra_drm_file *file); void tegra_drm_mapping_put(struct tegra_drm_mapping *mapping);