162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2021, NVIDIA Corporation.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/device.h>
762306a36Sopenharmony_ci#include <linux/kref.h>
862306a36Sopenharmony_ci#include <linux/of.h>
962306a36Sopenharmony_ci#include <linux/of_device.h>
1062306a36Sopenharmony_ci#include <linux/pid.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "context.h"
1462306a36Sopenharmony_ci#include "dev.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic void host1x_memory_context_release(struct device *dev)
1762306a36Sopenharmony_ci{
1862306a36Sopenharmony_ci	/* context device is freed in host1x_memory_context_list_free() */
1962306a36Sopenharmony_ci}
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciint host1x_memory_context_list_init(struct host1x *host1x)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	struct host1x_memory_context_list *cdl = &host1x->context_list;
2462306a36Sopenharmony_ci	struct device_node *node = host1x->dev->of_node;
2562306a36Sopenharmony_ci	struct host1x_memory_context *ctx;
2662306a36Sopenharmony_ci	unsigned int i;
2762306a36Sopenharmony_ci	int err;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	cdl->devs = NULL;
3062306a36Sopenharmony_ci	cdl->len = 0;
3162306a36Sopenharmony_ci	mutex_init(&cdl->lock);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	err = of_property_count_u32_elems(node, "iommu-map");
3462306a36Sopenharmony_ci	if (err < 0)
3562306a36Sopenharmony_ci		return 0;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	cdl->len = err / 4;
3862306a36Sopenharmony_ci	cdl->devs = kcalloc(cdl->len, sizeof(*cdl->devs), GFP_KERNEL);
3962306a36Sopenharmony_ci	if (!cdl->devs)
4062306a36Sopenharmony_ci		return -ENOMEM;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	for (i = 0; i < cdl->len; i++) {
4362306a36Sopenharmony_ci		ctx = &cdl->devs[i];
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci		ctx->host = host1x;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci		device_initialize(&ctx->dev);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci		/*
5062306a36Sopenharmony_ci		 * Due to an issue with T194 NVENC, only 38 bits can be used.
5162306a36Sopenharmony_ci		 * Anyway, 256GiB of IOVA ought to be enough for anyone.
5262306a36Sopenharmony_ci		 */
5362306a36Sopenharmony_ci		ctx->dma_mask = DMA_BIT_MASK(38);
5462306a36Sopenharmony_ci		ctx->dev.dma_mask = &ctx->dma_mask;
5562306a36Sopenharmony_ci		ctx->dev.coherent_dma_mask = ctx->dma_mask;
5662306a36Sopenharmony_ci		dev_set_name(&ctx->dev, "host1x-ctx.%d", i);
5762306a36Sopenharmony_ci		ctx->dev.bus = &host1x_context_device_bus_type;
5862306a36Sopenharmony_ci		ctx->dev.parent = host1x->dev;
5962306a36Sopenharmony_ci		ctx->dev.release = host1x_memory_context_release;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci		dma_set_max_seg_size(&ctx->dev, UINT_MAX);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci		err = device_add(&ctx->dev);
6462306a36Sopenharmony_ci		if (err) {
6562306a36Sopenharmony_ci			dev_err(host1x->dev, "could not add context device %d: %d\n", i, err);
6662306a36Sopenharmony_ci			put_device(&ctx->dev);
6762306a36Sopenharmony_ci			goto unreg_devices;
6862306a36Sopenharmony_ci		}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci		err = of_dma_configure_id(&ctx->dev, node, true, &i);
7162306a36Sopenharmony_ci		if (err) {
7262306a36Sopenharmony_ci			dev_err(host1x->dev, "IOMMU configuration failed for context device %d: %d\n",
7362306a36Sopenharmony_ci				i, err);
7462306a36Sopenharmony_ci			device_unregister(&ctx->dev);
7562306a36Sopenharmony_ci			goto unreg_devices;
7662306a36Sopenharmony_ci		}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci		if (!tegra_dev_iommu_get_stream_id(&ctx->dev, &ctx->stream_id) ||
7962306a36Sopenharmony_ci		    !device_iommu_mapped(&ctx->dev)) {
8062306a36Sopenharmony_ci			dev_err(host1x->dev, "Context device %d has no IOMMU!\n", i);
8162306a36Sopenharmony_ci			device_unregister(&ctx->dev);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci			/*
8462306a36Sopenharmony_ci			 * This means that if IOMMU is disabled but context devices
8562306a36Sopenharmony_ci			 * are defined in the device tree, Host1x will fail to probe.
8662306a36Sopenharmony_ci			 * That's probably OK in this time and age.
8762306a36Sopenharmony_ci			 */
8862306a36Sopenharmony_ci			err = -EINVAL;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci			goto unreg_devices;
9162306a36Sopenharmony_ci		}
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ciunreg_devices:
9762306a36Sopenharmony_ci	while (i--)
9862306a36Sopenharmony_ci		device_unregister(&cdl->devs[i].dev);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	kfree(cdl->devs);
10162306a36Sopenharmony_ci	cdl->devs = NULL;
10262306a36Sopenharmony_ci	cdl->len = 0;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return err;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_civoid host1x_memory_context_list_free(struct host1x_memory_context_list *cdl)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	unsigned int i;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	for (i = 0; i < cdl->len; i++)
11262306a36Sopenharmony_ci		device_unregister(&cdl->devs[i].dev);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	kfree(cdl->devs);
11562306a36Sopenharmony_ci	cdl->len = 0;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistruct host1x_memory_context *host1x_memory_context_alloc(struct host1x *host1x,
11962306a36Sopenharmony_ci							  struct device *dev,
12062306a36Sopenharmony_ci							  struct pid *pid)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct host1x_memory_context_list *cdl = &host1x->context_list;
12362306a36Sopenharmony_ci	struct host1x_memory_context *free = NULL;
12462306a36Sopenharmony_ci	int i;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (!cdl->len)
12762306a36Sopenharmony_ci		return ERR_PTR(-EOPNOTSUPP);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	mutex_lock(&cdl->lock);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	for (i = 0; i < cdl->len; i++) {
13262306a36Sopenharmony_ci		struct host1x_memory_context *cd = &cdl->devs[i];
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		if (cd->dev.iommu->iommu_dev != dev->iommu->iommu_dev)
13562306a36Sopenharmony_ci			continue;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		if (cd->owner == pid) {
13862306a36Sopenharmony_ci			refcount_inc(&cd->ref);
13962306a36Sopenharmony_ci			mutex_unlock(&cdl->lock);
14062306a36Sopenharmony_ci			return cd;
14162306a36Sopenharmony_ci		} else if (!cd->owner && !free) {
14262306a36Sopenharmony_ci			free = cd;
14362306a36Sopenharmony_ci		}
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if (!free) {
14762306a36Sopenharmony_ci		mutex_unlock(&cdl->lock);
14862306a36Sopenharmony_ci		return ERR_PTR(-EBUSY);
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	refcount_set(&free->ref, 1);
15262306a36Sopenharmony_ci	free->owner = get_pid(pid);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	mutex_unlock(&cdl->lock);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	return free;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(host1x_memory_context_alloc);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_civoid host1x_memory_context_get(struct host1x_memory_context *cd)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	refcount_inc(&cd->ref);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(host1x_memory_context_get);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_civoid host1x_memory_context_put(struct host1x_memory_context *cd)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	struct host1x_memory_context_list *cdl = &cd->host->context_list;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (refcount_dec_and_mutex_lock(&cd->ref, &cdl->lock)) {
17162306a36Sopenharmony_ci		put_pid(cd->owner);
17262306a36Sopenharmony_ci		cd->owner = NULL;
17362306a36Sopenharmony_ci		mutex_unlock(&cdl->lock);
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(host1x_memory_context_put);
177