| // SPDX-License-Identifier: GPL-2.0-only | |
| /* Copyright (c) 2020 NVIDIA Corporation */ | |
| #include <linux/host1x.h> | |
| #include <linux/iommu.h> | |
| #include <linux/list.h> | |
| #include <drm/drm_drv.h> | |
| #include <drm/drm_file.h> | |
| #include <drm/drm_utils.h> | |
| #include "drm.h" | |
| #include "uapi.h" | |
| static void tegra_drm_mapping_release(struct kref *ref) | |
| { | |
| struct tegra_drm_mapping *mapping = | |
| container_of(ref, struct tegra_drm_mapping, ref); | |
| host1x_bo_unpin(mapping->map); | |
| host1x_bo_put(mapping->bo); | |
| kfree(mapping); | |
| } | |
| void tegra_drm_mapping_put(struct tegra_drm_mapping *mapping) | |
| { | |
| kref_put(&mapping->ref, tegra_drm_mapping_release); | |
| } | |
| static void tegra_drm_channel_context_close(struct tegra_drm_context *context) | |
| { | |
| struct tegra_drm_mapping *mapping; | |
| unsigned long id; | |
| if (context->memory_context) | |
| host1x_memory_context_put(context->memory_context); | |
| xa_for_each(&context->mappings, id, mapping) | |
| tegra_drm_mapping_put(mapping); | |
| xa_destroy(&context->mappings); | |
| host1x_channel_put(context->channel); | |
| kfree(context); | |
| } | |
| void tegra_drm_uapi_close_file(struct tegra_drm_file *file) | |
| { | |
| struct tegra_drm_context *context; | |
| struct host1x_syncpt *sp; | |
| unsigned long id; | |
| xa_for_each(&file->contexts, id, context) | |
| tegra_drm_channel_context_close(context); | |
| xa_for_each(&file->syncpoints, id, sp) | |
| host1x_syncpt_put(sp); | |
| xa_destroy(&file->contexts); | |
| xa_destroy(&file->syncpoints); | |
| } | |
| static struct tegra_drm_client *tegra_drm_find_client(struct tegra_drm *tegra, u32 class) | |
| { | |
| struct tegra_drm_client *client; | |
| list_for_each_entry(client, &tegra->clients, list) | |
| if (client->base.class == class) | |
| return client; | |
| return NULL; | |
| } | |
| int tegra_drm_ioctl_channel_open(struct drm_device *drm, void *data, struct drm_file *file) | |
| { | |
| struct host1x *host = tegra_drm_to_host1x(drm->dev_private); | |
| struct tegra_drm_file *fpriv = file->driver_priv; | |
| struct tegra_drm *tegra = drm->dev_private; | |
| struct drm_tegra_channel_open *args = data; | |
| struct tegra_drm_client *client = NULL; | |
| struct tegra_drm_context *context; | |
| int err; | |
| if (args->flags) | |
| return -EINVAL; | |
| context = kzalloc_obj(*context); | |
| if (!context) | |
| return -ENOMEM; | |
| client = tegra_drm_find_client(tegra, args->host1x_class); | |
| if (!client) { | |
| err = -ENODEV; | |
| goto free; | |
| } | |
| if (client->shared_channel) { | |
| context->channel = host1x_channel_get(client->shared_channel); | |
| } else { | |
| context->channel = host1x_channel_request(&client->base); | |
| if (!context->channel) { | |
| err = -EBUSY; | |
| goto free; | |
| } | |
| } | |
| /* Only allocate context if the engine supports context isolation. */ | |
| if (device_iommu_mapped(client->base.dev) && client->ops->can_use_memory_ctx) { | |
| bool supported; | |
| err = client->ops->can_use_memory_ctx(client, &supported); | |
| if (err) | |
| goto put_channel; | |
| if (supported) { | |
| struct pid *pid = get_task_pid(current, PIDTYPE_TGID); | |
| context->memory_context = host1x_memory_context_alloc( | |
| host, client->base.dev, pid); | |
| put_pid(pid); | |
| } | |
| if (IS_ERR(context->memory_context)) { | |
| if (PTR_ERR(context->memory_context) != -EOPNOTSUPP) { | |
| err = PTR_ERR(context->memory_context); | |
| goto put_channel; | |
| } else { | |
| /* | |
| * OK, HW does not support contexts or contexts | |
| * are disabled. | |
| */ | |
| context->memory_context = NULL; | |
| } | |
| } | |
| } | |
| err = xa_alloc(&fpriv->contexts, &args->context, context, XA_LIMIT(1, U32_MAX), | |
| GFP_KERNEL); | |
| if (err < 0) | |
| goto put_memctx; | |
| context->client = client; | |
| xa_init_flags(&context->mappings, XA_FLAGS_ALLOC1); | |
| args->version = client->version; | |
| args->capabilities = 0; | |
| if (device_get_dma_attr(client->base.dev) == DEV_DMA_COHERENT) | |
| args->capabilities |= DRM_TEGRA_CHANNEL_CAP_CACHE_COHERENT; | |
| return 0; | |
| put_memctx: | |
| if (context->memory_context) | |
| host1x_memory_context_put(context->memory_context); | |
| put_channel: | |
| host1x_channel_put(context->channel); | |
| free: | |
| kfree(context); | |
| return err; | |
| } | |
| int tegra_drm_ioctl_channel_close(struct drm_device *drm, void *data, struct drm_file *file) | |
| { | |
| struct tegra_drm_file *fpriv = file->driver_priv; | |
| struct drm_tegra_channel_close *args = data; | |
| struct tegra_drm_context *context; | |
| mutex_lock(&fpriv->lock); | |
| context = xa_load(&fpriv->contexts, args->context); | |
| if (!context) { | |
| mutex_unlock(&fpriv->lock); | |
| return -EINVAL; | |
| } | |
| xa_erase(&fpriv->contexts, args->context); | |
| mutex_unlock(&fpriv->lock); | |
| tegra_drm_channel_context_close(context); | |
| return 0; | |
| } | |
| int tegra_drm_ioctl_channel_map(struct drm_device *drm, void *data, struct drm_file *file) | |
| { | |
| struct tegra_drm_file *fpriv = file->driver_priv; | |
| struct drm_tegra_channel_map *args = data; | |
| struct tegra_drm_mapping *mapping; | |
| struct tegra_drm_context *context; | |
| enum dma_data_direction direction; | |
| struct device *mapping_dev; | |
| int err = 0; | |
| if (args->flags & ~DRM_TEGRA_CHANNEL_MAP_READ_WRITE) | |
| return -EINVAL; | |
| mutex_lock(&fpriv->lock); | |
| context = xa_load(&fpriv->contexts, args->context); | |
| if (!context) { | |
| mutex_unlock(&fpriv->lock); | |
| return -EINVAL; | |
| } | |
| mapping = kzalloc_obj(*mapping); | |
| if (!mapping) { | |
| err = -ENOMEM; | |
| goto unlock; | |
| } | |
| kref_init(&mapping->ref); | |
| if (context->memory_context) | |
| mapping_dev = &context->memory_context->dev; | |
| else | |
| mapping_dev = context->client->base.dev; | |
| mapping->bo = tegra_gem_lookup(file, args->handle); | |
| if (!mapping->bo) { | |
| err = -EINVAL; | |
| goto free; | |
| } | |
| switch (args->flags & DRM_TEGRA_CHANNEL_MAP_READ_WRITE) { | |
| case DRM_TEGRA_CHANNEL_MAP_READ_WRITE: | |
| direction = DMA_BIDIRECTIONAL; | |
| break; | |
| case DRM_TEGRA_CHANNEL_MAP_WRITE: | |
| direction = DMA_FROM_DEVICE; | |
| break; | |
| case DRM_TEGRA_CHANNEL_MAP_READ: | |
| direction = DMA_TO_DEVICE; | |
| break; | |
| default: | |
| err = -EINVAL; | |
| goto put_gem; | |
| } | |
| mapping->map = host1x_bo_pin(mapping_dev, mapping->bo, direction, NULL); | |
| if (IS_ERR(mapping->map)) { | |
| err = PTR_ERR(mapping->map); | |
| goto put_gem; | |
| } | |
| mapping->iova = mapping->map->phys; | |
| mapping->iova_end = mapping->iova + host1x_to_tegra_bo(mapping->bo)->gem.size; | |
| err = xa_alloc(&context->mappings, &args->mapping, mapping, XA_LIMIT(1, U32_MAX), | |
| GFP_KERNEL); | |
| if (err < 0) | |
| goto unpin; | |
| mutex_unlock(&fpriv->lock); | |
| return 0; | |
| unpin: | |
| host1x_bo_unpin(mapping->map); | |
| put_gem: | |
| host1x_bo_put(mapping->bo); | |
| free: | |
| kfree(mapping); | |
| unlock: | |
| mutex_unlock(&fpriv->lock); | |
| return err; | |
| } | |
| int tegra_drm_ioctl_channel_unmap(struct drm_device *drm, void *data, struct drm_file *file) | |
| { | |
| struct tegra_drm_file *fpriv = file->driver_priv; | |
| struct drm_tegra_channel_unmap *args = data; | |
| struct tegra_drm_mapping *mapping; | |
| struct tegra_drm_context *context; | |
| mutex_lock(&fpriv->lock); | |
| context = xa_load(&fpriv->contexts, args->context); | |
| if (!context) { | |
| mutex_unlock(&fpriv->lock); | |
| return -EINVAL; | |
| } | |
| mapping = xa_erase(&context->mappings, args->mapping); | |
| mutex_unlock(&fpriv->lock); | |
| if (!mapping) | |
| return -EINVAL; | |
| tegra_drm_mapping_put(mapping); | |
| return 0; | |
| } | |
| int tegra_drm_ioctl_syncpoint_allocate(struct drm_device *drm, void *data, struct drm_file *file) | |
| { | |
| struct host1x *host1x = tegra_drm_to_host1x(drm->dev_private); | |
| struct tegra_drm_file *fpriv = file->driver_priv; | |
| struct drm_tegra_syncpoint_allocate *args = data; | |
| struct host1x_syncpt *sp; | |
| int err; | |
| if (args->id) | |
| return -EINVAL; | |
| sp = host1x_syncpt_alloc(host1x, HOST1X_SYNCPT_CLIENT_MANAGED, current->comm); | |
| if (!sp) | |
| return -EBUSY; | |
| args->id = host1x_syncpt_id(sp); | |
| err = xa_insert(&fpriv->syncpoints, args->id, sp, GFP_KERNEL); | |
| if (err) { | |
| host1x_syncpt_put(sp); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| int tegra_drm_ioctl_syncpoint_free(struct drm_device *drm, void *data, struct drm_file *file) | |
| { | |
| struct tegra_drm_file *fpriv = file->driver_priv; | |
| struct drm_tegra_syncpoint_allocate *args = data; | |
| struct host1x_syncpt *sp; | |
| mutex_lock(&fpriv->lock); | |
| sp = xa_erase(&fpriv->syncpoints, args->id); | |
| mutex_unlock(&fpriv->lock); | |
| if (!sp) | |
| return -EINVAL; | |
| host1x_syncpt_put(sp); | |
| return 0; | |
| } | |
| int tegra_drm_ioctl_syncpoint_wait(struct drm_device *drm, void *data, struct drm_file *file) | |
| { | |
| struct host1x *host1x = tegra_drm_to_host1x(drm->dev_private); | |
| struct drm_tegra_syncpoint_wait *args = data; | |
| signed long timeout_jiffies; | |
| struct host1x_syncpt *sp; | |
| if (args->padding != 0) | |
| return -EINVAL; | |
| sp = host1x_syncpt_get_by_id_noref(host1x, args->id); | |
| if (!sp) | |
| return -EINVAL; | |
| timeout_jiffies = drm_timeout_abs_to_jiffies(args->timeout_ns); | |
| return host1x_syncpt_wait(sp, args->threshold, timeout_jiffies, &args->value); | |
| } |