// 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);}
// 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);
}