From 5f7d8f89c6beecafc1248d350dd46cd0d02441d5 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 19 Nov 2024 07:07:34 +0000 Subject: [PATCH] drm/tegra: Add syncpoint memory export interface Add a userspace interface for providing access to the memory syncpoint shim through dma-buf file descriptors. Applications can pass references to syncpoints to other devices that only support memory semaphores to facilitate synchronization with syncpoints. Only syncpoints allocated through the DRM file descriptor ("owned" by the application) can be exported read-write. All syncpoints can be exported read-only. Jira HOSTX-5722 Bug 4919132 Change-Id: I4f5c007b7f9ba65780c418b4e22c5f59a11ef84d Signed-off-by: Mikko Perttunen Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3252026 Reviewed-by: Santosh BS GVS: buildbot_gerritrpt Reviewed-by: mobile promotions Tested-by: mobile promotions --- drivers/gpu/drm/tegra/drm.c | 2 + .../tegra/include/uapi/drm/tegra_drm_next.h | 42 ++++ drivers/gpu/drm/tegra/uapi.c | 184 ++++++++++++++++++ drivers/gpu/drm/tegra/uapi.h | 2 + 4 files changed, 230 insertions(+) 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);