mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
In Linux v6.12 the definition 'no_llseek' was finally removed. Since Linux v6.0 it had been redefined as NULL. Add a test to conftest to determine if 'no_llseek' is present and if not then it is no longer necessary to populate this and we can leave as NULL. Bug 4876974 Change-Id: I051fdb285b32260b5913dad89cabe0be04253f67 Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3222106 Tested-by: mobile promotions <svcmobile_promotions@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
1147 lines
28 KiB
C
1147 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// SPDX-FileCopyrightText: Copyright (c) 2016-2024, NVIDIA CORPORATION. All rights reserved.
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/nvhost.h>
|
|
#include <linux/semaphore.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/circ_buf.h>
|
|
#include <linux/nospec.h>
|
|
#include <asm/ioctls.h>
|
|
#include <asm/barrier.h>
|
|
#include <linux/kref.h>
|
|
#include <uapi/linux/nvdev_fence.h>
|
|
#include <uapi/linux/nvpva_ioctl.h>
|
|
#include <linux/firmware.h>
|
|
|
|
#include "pva.h"
|
|
#include "pva_queue.h"
|
|
#include "nvpva_buffer.h"
|
|
#include "pva_vpu_exe.h"
|
|
#include "pva_vpu_app_auth.h"
|
|
#include "pva_system_allow_list.h"
|
|
#include "nvpva_client.h"
|
|
/**
|
|
* @brief pva_private - Per-fd specific data
|
|
*
|
|
* pdev Pointer the pva device
|
|
* queue Pointer the struct nvpva_queue
|
|
* buffer Pointer to the struct nvpva_buffer
|
|
*/
|
|
struct pva_private {
|
|
struct pva *pva;
|
|
struct nvpva_queue *queue;
|
|
struct pva_cb *vpu_print_buffer;
|
|
struct nvpva_client_context *client;
|
|
};
|
|
|
|
static int copy_part_from_user(void *kbuffer, size_t kbuffer_size,
|
|
struct nvpva_ioctl_part part)
|
|
{
|
|
int err = 0;
|
|
int copy_ret;
|
|
|
|
if (part.size == 0)
|
|
goto out;
|
|
|
|
if (kbuffer_size < part.size) {
|
|
pr_err("pva: failed to copy from user due to size too large: %llu > %lu",
|
|
part.size, kbuffer_size);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
copy_ret =
|
|
copy_from_user(kbuffer, (void __user *)part.addr, part.size);
|
|
if (copy_ret) {
|
|
err = -EFAULT;
|
|
goto out;
|
|
}
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static struct pva_cb *pva_alloc_cb(struct device *dev, uint32_t size)
|
|
{
|
|
int err;
|
|
struct pva_cb *cb;
|
|
|
|
if ((size == 0) || (((size - 1) & size) != 0)) {
|
|
dev_err(dev, "invalid circular buffer size: %u; it must be 2^N.", size);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
cb = kzalloc(sizeof(*cb), GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(cb)) {
|
|
err = PTR_ERR(cb);
|
|
goto out;
|
|
}
|
|
|
|
cb->size = size;
|
|
cb->buffer_va =
|
|
dma_alloc_coherent(dev, cb->size, &cb->buffer_addr, GFP_KERNEL);
|
|
|
|
if (IS_ERR_OR_NULL(cb->buffer_va)) {
|
|
err = PTR_ERR(cb->buffer_va);
|
|
goto free_mem;
|
|
}
|
|
|
|
cb->head_va = dma_alloc_coherent(dev, sizeof(uint32_t), &cb->head_addr,
|
|
GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(cb->head_va)) {
|
|
err = PTR_ERR(cb->head_va);
|
|
goto free_buffer;
|
|
}
|
|
|
|
cb->tail_va = dma_alloc_coherent(dev, sizeof(uint32_t), &cb->tail_addr,
|
|
GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(cb->tail_va)) {
|
|
err = PTR_ERR(cb->tail_va);
|
|
goto free_head;
|
|
}
|
|
|
|
cb->err_va = dma_alloc_coherent(dev, sizeof(uint32_t), &cb->err_addr,
|
|
GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(cb->err_va)) {
|
|
err = PTR_ERR(cb->err_va);
|
|
goto free_tail;
|
|
}
|
|
|
|
*cb->head_va = 0;
|
|
cb->tail = 0;
|
|
*cb->tail_va = cb->tail;
|
|
*cb->err_va = 0;
|
|
return cb;
|
|
|
|
free_tail:
|
|
dma_free_coherent(dev, sizeof(uint32_t), cb->tail_va, cb->tail_addr);
|
|
free_head:
|
|
dma_free_coherent(dev, sizeof(uint32_t), cb->head_va, cb->head_addr);
|
|
free_buffer:
|
|
dma_free_coherent(dev, cb->size, cb->buffer_va, cb->buffer_addr);
|
|
free_mem:
|
|
kfree(cb);
|
|
out:
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static void pva_free_cb(struct device *dev, struct pva_cb *cb)
|
|
{
|
|
dma_free_coherent(dev, sizeof(uint32_t), cb->tail_va, cb->tail_addr);
|
|
dma_free_coherent(dev, sizeof(uint32_t), cb->head_va, cb->head_addr);
|
|
dma_free_coherent(dev, sizeof(uint32_t), cb->err_va, cb->err_addr);
|
|
dma_free_coherent(dev, cb->size, cb->buffer_va, cb->buffer_addr);
|
|
kfree(cb);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Copy a single task from userspace to kernel space
|
|
*
|
|
* This function copies fields from ioctl_task and performs a deep copy
|
|
* of the task to kernel memory. At the same time, input values shall
|
|
* be validated. This allows using all the fields without manually performing
|
|
* copies of the structure and performing checks later.
|
|
*
|
|
* @param ioctl_task Pointer to a userspace task that is copied
|
|
* to kernel memory
|
|
* @param task Pointer to a task that should be created
|
|
* @return 0 on Success or negative error code
|
|
*
|
|
*/
|
|
static int pva_copy_task(struct nvpva_ioctl_task *ioctl_task,
|
|
struct pva_submit_task *task)
|
|
{
|
|
int err = 0;
|
|
u32 i;
|
|
struct pva_elf_image *image = NULL;
|
|
|
|
nvpva_dbg_fn(task->pva, "");
|
|
/*
|
|
* These fields are clear-text in the task descriptor. Just
|
|
* copy them.
|
|
*/
|
|
task->exe_id1 = ioctl_task->exe_id1;
|
|
if (task->pva->version <= PVA_HW_GEN2)
|
|
task->exe_id2 = NVPVA_NOOP_EXE_ID;
|
|
else
|
|
task->exe_id2 = ioctl_task->exe_id2;
|
|
|
|
task->stream_id = ioctl_task->stream_id;
|
|
task->prog_id = ioctl_task->prog_id;
|
|
task->l2_alloc_size = ioctl_task->l2_alloc_size;
|
|
task->symbol_payload_size = ioctl_task->symbol_payload.size;
|
|
task->flags = ioctl_task->flags;
|
|
if (task->exe_id1 < NVPVA_NOOP_EXE_ID)
|
|
image = get_elf_image(&task->client->elf_ctx, task->exe_id1);
|
|
|
|
task->is_system_app = (image != NULL) && image->is_system_app;
|
|
|
|
#define IOCTL_ARRAY_SIZE(field_name) \
|
|
(ioctl_task->field_name.size / sizeof(task->field_name[0]))
|
|
|
|
task->num_prefences = IOCTL_ARRAY_SIZE(prefences);
|
|
task->num_user_fence_actions = IOCTL_ARRAY_SIZE(user_fence_actions);
|
|
task->num_input_task_status = IOCTL_ARRAY_SIZE(input_task_status);
|
|
task->num_output_task_status = IOCTL_ARRAY_SIZE(output_task_status);
|
|
task->num_dma_descriptors = IOCTL_ARRAY_SIZE(dma_descriptors);
|
|
task->num_dma_channels = IOCTL_ARRAY_SIZE(dma_channels);
|
|
task->num_symbols = IOCTL_ARRAY_SIZE(symbols);
|
|
|
|
#undef IOCTL_ARRAY_SIZE
|
|
|
|
err = copy_part_from_user(&task->prefences, sizeof(task->prefences),
|
|
ioctl_task->prefences);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->user_fence_actions,
|
|
sizeof(task->user_fence_actions),
|
|
ioctl_task->user_fence_actions);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->input_task_status,
|
|
sizeof(task->input_task_status),
|
|
ioctl_task->input_task_status);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->output_task_status,
|
|
sizeof(task->output_task_status),
|
|
ioctl_task->output_task_status);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->dma_descriptors,
|
|
sizeof(task->dma_descriptors),
|
|
ioctl_task->dma_descriptors);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->dma_channels,
|
|
sizeof(task->dma_channels),
|
|
ioctl_task->dma_channels);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (task->is_system_app)
|
|
err = copy_part_from_user(&task->dma_misr_config,
|
|
sizeof(task->dma_misr_config),
|
|
ioctl_task->dma_misr_config);
|
|
else
|
|
task->dma_misr_config.enable = 0;
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->hwseq_config,
|
|
sizeof(task->hwseq_config),
|
|
ioctl_task->hwseq_config);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->symbols, sizeof(task->symbols),
|
|
ioctl_task->symbols);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = copy_part_from_user(&task->symbol_payload,
|
|
sizeof(task->symbol_payload),
|
|
ioctl_task->symbol_payload);
|
|
if (err)
|
|
goto out;
|
|
|
|
/* Parse each postfence provided by user in 1D array and store into
|
|
* internal 2D array representation wrt type of fence and number of
|
|
* fences of each type for further processing
|
|
*/
|
|
for (i = 0; i < task->num_user_fence_actions; i++) {
|
|
struct nvpva_fence_action *fence = &task->user_fence_actions[i];
|
|
enum nvpva_fence_action_type fence_type = fence->type;
|
|
u8 num_fence;
|
|
|
|
if ((fence_type == 0U) ||
|
|
(fence_type >= NVPVA_MAX_FENCE_TYPES)) {
|
|
task_err(task, "invalid fence type at index: %u", i);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Ensure that the number of postfences for each type are within
|
|
* limit
|
|
*/
|
|
num_fence = task->num_pva_fence_actions[fence_type];
|
|
if (num_fence >= NVPVA_TASK_MAX_FENCEACTIONS) {
|
|
task_err(task, "too many fences for type: %u",
|
|
fence_type);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
task->pva_fence_actions[fence_type][num_fence] = *fence;
|
|
task->num_pva_fence_actions[fence_type] += 1;
|
|
}
|
|
|
|
/* Check for valid HWSeq trigger mode */
|
|
if ((task->hwseq_config.hwseqTrigMode != NVPVA_HWSEQTM_VPUTRIG) &&
|
|
(task->hwseq_config.hwseqTrigMode != NVPVA_HWSEQTM_DMATRIG)) {
|
|
task_err(task, "invalid hwseq trigger mode: %d",
|
|
task->hwseq_config.hwseqTrigMode);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
#undef COPY_FIELD
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* @brief Submit a task to PVA
|
|
*
|
|
* This function takes the given list of tasks, converts
|
|
* them into kernel internal representation and submits
|
|
* them to the task queue. On success, it populates
|
|
* the post-fence structures in userspace and returns 0.
|
|
*
|
|
* @param priv PVA Private data
|
|
* @param arg ioctl data
|
|
* @return 0 on Success or negative error code
|
|
*
|
|
*/
|
|
static int pva_submit(struct pva_private *priv, void *arg)
|
|
{
|
|
struct nvpva_ioctl_submit_in_arg *ioctl_tasks_header =
|
|
(struct nvpva_ioctl_submit_in_arg *)arg;
|
|
struct nvpva_ioctl_task *ioctl_tasks = NULL;
|
|
struct pva_submit_tasks *tasks_header;
|
|
int err = 0;
|
|
unsigned long rest;
|
|
int i, j;
|
|
uint32_t num_tasks;
|
|
|
|
num_tasks = ioctl_tasks_header->tasks.size / sizeof(*ioctl_tasks);
|
|
/* Sanity checks for the task heaader */
|
|
if (num_tasks > NVPVA_SUBMIT_MAX_TASKS) {
|
|
err = -EINVAL;
|
|
dev_err(&priv->pva->pdev->dev,
|
|
"exceeds maximum number of tasks: %u > %u", num_tasks,
|
|
NVPVA_SUBMIT_MAX_TASKS);
|
|
goto out;
|
|
}
|
|
|
|
num_tasks = array_index_nospec(num_tasks, NVPVA_SUBMIT_MAX_TASKS + 1);
|
|
if (ioctl_tasks_header->version > 0) {
|
|
err = -ENOSYS;
|
|
goto out;
|
|
}
|
|
|
|
|
|
/* Allocate memory for the UMD representation of the tasks */
|
|
ioctl_tasks = kzalloc(ioctl_tasks_header->tasks.size, GFP_KERNEL);
|
|
if (ioctl_tasks == NULL) {
|
|
pr_err("pva: submit: allocation for tasks failed");
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
tasks_header = kzalloc(sizeof(struct pva_submit_tasks), GFP_KERNEL);
|
|
if (tasks_header == NULL) {
|
|
pr_err("pva: submit: allocation for tasks_header failed");
|
|
kfree(ioctl_tasks);
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Copy the tasks from userspace */
|
|
rest = copy_from_user(ioctl_tasks,
|
|
(void __user *)ioctl_tasks_header->tasks.addr,
|
|
ioctl_tasks_header->tasks.size);
|
|
|
|
if (rest > 0) {
|
|
err = -EFAULT;
|
|
pr_err("pva: failed to copy tasks");
|
|
goto free_ioctl_tasks;
|
|
}
|
|
|
|
tasks_header->num_tasks = 0;
|
|
|
|
/* Go through the tasks and make a KMD representation of them */
|
|
for (i = 0; i < num_tasks; i++) {
|
|
struct pva_submit_task *task;
|
|
struct nvpva_queue_task_mem_info task_mem_info;
|
|
long timeout_jiffies = usecs_to_jiffies(
|
|
ioctl_tasks_header->submission_timeout_us);
|
|
|
|
/* Allocate memory for the task and dma */
|
|
err = down_timeout(&priv->queue->task_pool_sem,
|
|
timeout_jiffies);
|
|
if (err) {
|
|
pr_err("pva: timeout when allocating task buffer");
|
|
/* UMD expects this error code */
|
|
err = -EAGAIN;
|
|
goto free_tasks;
|
|
}
|
|
err = nvpva_queue_alloc_task_memory(priv->queue,
|
|
&task_mem_info);
|
|
task = task_mem_info.kmem_addr;
|
|
|
|
WARN_ON((err < 0) || !task);
|
|
|
|
/* initialize memory to 0 */
|
|
(void)memset(task_mem_info.kmem_addr, 0,
|
|
priv->queue->task_kmem_size);
|
|
(void)memset(task_mem_info.va, 0, priv->queue->task_dma_size);
|
|
|
|
/* Obtain an initial reference */
|
|
kref_init(&task->ref);
|
|
INIT_LIST_HEAD(&task->node);
|
|
|
|
tasks_header->tasks[i] = task;
|
|
tasks_header->num_tasks += 1;
|
|
|
|
task->dma_addr = task_mem_info.dma_addr;
|
|
task->aux_dma_addr = task_mem_info.aux_dma_addr;
|
|
task->va = task_mem_info.va;
|
|
task->aux_va = task_mem_info.aux_va;
|
|
task->pool_index = task_mem_info.pool_index;
|
|
|
|
task->pva = priv->pva;
|
|
task->queue = priv->queue;
|
|
task->client = priv->client;
|
|
|
|
/* setup ownership */
|
|
err = nvhost_module_busy(task->pva->pdev);
|
|
if (err)
|
|
goto free_tasks;
|
|
|
|
nvpva_client_context_get(task->client);
|
|
|
|
err = pva_copy_task(ioctl_tasks + i, task);
|
|
if (err)
|
|
goto free_tasks;
|
|
|
|
if (priv->pva->vpu_printf_enabled)
|
|
task->stdout = priv->vpu_print_buffer;
|
|
}
|
|
|
|
/* Populate header structure */
|
|
tasks_header->execution_timeout_us =
|
|
ioctl_tasks_header->execution_timeout_us;
|
|
|
|
/* TODO: submission timeout */
|
|
/* ..and submit them */
|
|
err = nvpva_queue_submit(priv->queue, tasks_header);
|
|
|
|
if (err < 0)
|
|
goto free_tasks;
|
|
|
|
/* Copy fences back to userspace */
|
|
for (i = 0; i < tasks_header->num_tasks; i++) {
|
|
struct pva_submit_task *task = tasks_header->tasks[i];
|
|
u32 n_copied[NVPVA_MAX_FENCE_TYPES] = {};
|
|
struct nvpva_fence_action __user *action_fences =
|
|
(struct nvpva_fence_action __user *)ioctl_tasks[i]
|
|
.user_fence_actions.addr;
|
|
|
|
/* Copy return postfences in the same order as that provided in
|
|
* input
|
|
*/
|
|
for (j = 0; j < task->num_user_fence_actions; j++) {
|
|
struct nvpva_fence_action *fence =
|
|
&task->user_fence_actions[j];
|
|
enum nvpva_fence_action_type fence_type = fence->type;
|
|
|
|
*fence = task->pva_fence_actions[fence_type]
|
|
[n_copied[fence_type]];
|
|
n_copied[fence_type] += 1;
|
|
}
|
|
|
|
rest = copy_to_user(action_fences, task->user_fence_actions,
|
|
ioctl_tasks[i].user_fence_actions.size);
|
|
|
|
if (rest) {
|
|
nvpva_warn(&priv->pva->pdev->dev,
|
|
"Failed to copy pva fences to userspace");
|
|
err = -EFAULT;
|
|
goto free_tasks;
|
|
}
|
|
}
|
|
|
|
free_tasks:
|
|
|
|
for (i = 0; i < tasks_header->num_tasks; i++) {
|
|
struct pva_submit_task *task = tasks_header->tasks[i];
|
|
/* Drop the reference */
|
|
kref_put(&task->ref, pva_task_free);
|
|
}
|
|
|
|
free_ioctl_tasks:
|
|
|
|
kfree(ioctl_tasks);
|
|
kfree(tasks_header);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int pva_pin(struct pva_private *priv, void *arg)
|
|
{
|
|
int err = 0;
|
|
struct dma_buf *dmabuf[1];
|
|
struct nvpva_pin_in_arg *in_arg = (struct nvpva_pin_in_arg *)arg;
|
|
struct nvpva_pin_out_arg *out_arg = (struct nvpva_pin_out_arg *)arg;
|
|
u64 serial_id = 0xFFFFFFFFFFFFFFFF;
|
|
|
|
dmabuf[0] = dma_buf_get(in_arg->pin.handle);
|
|
if (IS_ERR_OR_NULL(dmabuf[0])) {
|
|
dev_err(&priv->pva->pdev->dev, "invalid handle to pin: %u",
|
|
in_arg->pin.handle);
|
|
err = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
err = nvpva_buffer_pin(priv->client->buffers,
|
|
&dmabuf[0],
|
|
&in_arg->pin.offset,
|
|
&in_arg->pin.size,
|
|
&serial_id,
|
|
in_arg->pin.segment,
|
|
1,
|
|
&out_arg->pin_id,
|
|
&out_arg->error_code);
|
|
dma_buf_put(dmabuf[0]);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int pva_pin_ex(struct pva_private *priv, void *arg)
|
|
{
|
|
int err = 0;
|
|
struct dma_buf *dmabuf[1];
|
|
struct nvpva_pin_in_arg_ex *in_arg = (struct nvpva_pin_in_arg_ex *)arg;
|
|
struct nvpva_pin_out_arg *out_arg = (struct nvpva_pin_out_arg *)arg;
|
|
|
|
dmabuf[0] = dma_buf_get(in_arg->pin.handle);
|
|
if (IS_ERR_OR_NULL(dmabuf[0])) {
|
|
dev_err(&priv->pva->pdev->dev, "invalid handle to pin: %u",
|
|
in_arg->pin.handle);
|
|
err = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
err = nvpva_buffer_pin(priv->client->buffers,
|
|
&dmabuf[0],
|
|
&in_arg->pin.offset,
|
|
&in_arg->pin.size,
|
|
&in_arg->pin.serial_id,
|
|
in_arg->pin.segment,
|
|
1,
|
|
&out_arg->pin_id,
|
|
&out_arg->error_code);
|
|
dma_buf_put(dmabuf[0]);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int pva_unpin(struct pva_private *priv, void *arg)
|
|
{
|
|
int err = 0;
|
|
struct nvpva_unpin_in_arg *in_arg = (struct nvpva_unpin_in_arg *)arg;
|
|
|
|
nvpva_buffer_unpin_id(priv->client->buffers, &in_arg->pin_id, 1);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
pva_authenticate_vpu_app(struct pva *pva,
|
|
struct pva_vpu_auth_s *auth,
|
|
uint8_t *data,
|
|
u32 size,
|
|
bool is_sys)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!auth->pva_auth_enable)
|
|
goto out;
|
|
|
|
mutex_lock(&auth->allow_list_lock);
|
|
if (!auth->pva_auth_allow_list_parsed) {
|
|
if (is_sys)
|
|
err = pva_auth_allow_list_parse_buf(pva->pdev,
|
|
auth, pva_auth_allow_list_sys,
|
|
pva_auth_allow_list_sys_len);
|
|
else
|
|
err = pva_auth_allow_list_parse(pva->pdev, auth);
|
|
|
|
if (err) {
|
|
nvpva_warn(&pva->pdev->dev,
|
|
"allow list parse failed");
|
|
mutex_unlock(&auth->allow_list_lock);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&auth->allow_list_lock);
|
|
err = pva_vpu_check_sha256_key(pva,
|
|
auth->vpu_hash_keys,
|
|
data,
|
|
size);
|
|
if (err != 0)
|
|
nvpva_dbg_fn(pva, "app authentication failed");
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int pva_register_vpu_exec(struct pva_private *priv, void *arg)
|
|
{
|
|
struct nvpva_vpu_exe_register_in_arg *reg_in =
|
|
(struct nvpva_vpu_exe_register_in_arg *)arg;
|
|
struct nvpva_vpu_exe_register_out_arg *reg_out =
|
|
(struct nvpva_vpu_exe_register_out_arg *)arg;
|
|
struct pva_elf_image *image;
|
|
void *exec_data = NULL;
|
|
uint16_t exe_id;
|
|
bool is_system = false;
|
|
uint64_t data_size;
|
|
int err = 0;
|
|
|
|
data_size = reg_in->exe_data.size;
|
|
exec_data = kvmalloc(data_size, GFP_KERNEL);
|
|
if (exec_data == NULL) {
|
|
nvpva_err(&priv->pva->pdev->dev,
|
|
"failed to allocate memory for elf");
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
err = copy_part_from_user(exec_data, data_size,
|
|
reg_in->exe_data);
|
|
if (err) {
|
|
nvpva_err(&priv->pva->pdev->dev,
|
|
"failed to copy vpu exe data");
|
|
goto free_mem;
|
|
}
|
|
|
|
err = pva_authenticate_vpu_app(priv->pva,
|
|
&priv->pva->pva_auth,
|
|
(uint8_t *)exec_data,
|
|
data_size,
|
|
false);
|
|
if (err != 0) {
|
|
err = pva_authenticate_vpu_app(priv->pva,
|
|
&priv->pva->pva_auth_sys,
|
|
(uint8_t *)exec_data,
|
|
data_size,
|
|
true);
|
|
if (err != 0)
|
|
goto free_mem;
|
|
|
|
is_system = true;
|
|
}
|
|
|
|
err = pva_load_vpu_app(&priv->client->elf_ctx, exec_data,
|
|
data_size, &exe_id,
|
|
is_system,
|
|
priv->pva->version);
|
|
|
|
if (err) {
|
|
nvpva_err(&priv->pva->pdev->dev,
|
|
"failed to register vpu app");
|
|
goto free_mem;
|
|
}
|
|
|
|
reg_out->exe_id = exe_id;
|
|
image = get_elf_image(&priv->client->elf_ctx, exe_id);
|
|
reg_out->num_of_symbols = image->num_symbols -
|
|
image->num_sys_symbols;
|
|
reg_out->symbol_size_total = image->symbol_size_total;
|
|
|
|
free_mem:
|
|
|
|
if (exec_data != NULL)
|
|
kvfree(exec_data);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int pva_unregister_vpu_exec(struct pva_private *priv, void *arg)
|
|
{
|
|
struct nvpva_vpu_exe_unregister_in_arg *unreg_in =
|
|
(struct nvpva_vpu_exe_unregister_in_arg *)arg;
|
|
return pva_release_vpu_app(&priv->client->elf_ctx,
|
|
unreg_in->exe_id, false);
|
|
}
|
|
|
|
static int pva_get_symbol_id(struct pva_private *priv, void *arg)
|
|
{
|
|
struct nvpva_get_symbol_in_arg *symbol_in =
|
|
(struct nvpva_get_symbol_in_arg *)arg;
|
|
struct nvpva_get_symbol_out_arg *symbol_out =
|
|
(struct nvpva_get_symbol_out_arg *)arg;
|
|
char *symbol_buffer;
|
|
int err = 0;
|
|
uint64_t name_size = symbol_in->name.size;
|
|
struct pva_elf_symbol symbol = {0};
|
|
|
|
if (name_size > ELF_MAX_SYMBOL_LENGTH) {
|
|
nvpva_warn(&priv->pva->pdev->dev, "symbol size too large:%llu",
|
|
symbol_in->name.size);
|
|
name_size = ELF_MAX_SYMBOL_LENGTH;
|
|
}
|
|
|
|
symbol_buffer = kmalloc(name_size, GFP_KERNEL);
|
|
if (symbol_buffer == NULL) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
err = copy_from_user(symbol_buffer,
|
|
(void __user *)symbol_in->name.addr,
|
|
name_size);
|
|
if (err) {
|
|
nvpva_err(&priv->pva->pdev->dev,
|
|
"failed to copy all name from user");
|
|
goto free_mem;
|
|
}
|
|
|
|
if (symbol_buffer[name_size - 1] != '\0') {
|
|
nvpva_warn(&priv->pva->pdev->dev,
|
|
"symbol name not terminated with NULL");
|
|
symbol_buffer[name_size - 1] = '\0';
|
|
}
|
|
|
|
err = pva_get_sym_info(&priv->client->elf_ctx, symbol_in->exe_id,
|
|
symbol_buffer, &symbol);
|
|
if (err) {
|
|
goto free_mem;
|
|
}
|
|
|
|
symbol_out->symbol.id = symbol.symbolID;
|
|
symbol_out->symbol.size = symbol.size;
|
|
symbol_out->symbol.isPointer =
|
|
(symbol.type == (uint32_t)VMEM_TYPE_POINTER) ? 1U : 0U;
|
|
free_mem:
|
|
kfree(symbol_buffer);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int pva_get_symtab(struct pva_private *priv, void *arg)
|
|
{
|
|
struct nvpva_get_sym_tab_in_arg *sym_tab_in =
|
|
(struct nvpva_get_sym_tab_in_arg *)arg;
|
|
|
|
int err = 0;
|
|
struct nvpva_sym_info *sym_tab_buffer;
|
|
u64 tab_size;
|
|
|
|
err = pva_get_sym_tab_size(&priv->client->elf_ctx,
|
|
sym_tab_in->exe_id,
|
|
&tab_size);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (sym_tab_in->tab.size < tab_size) {
|
|
nvpva_err(&priv->pva->pdev->dev,
|
|
"symbol table size smaller than needed:%llu",
|
|
sym_tab_in->tab.size);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
sym_tab_buffer = kmalloc(tab_size, GFP_KERNEL);
|
|
if (sym_tab_buffer == NULL) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
err = pva_get_sym_tab(&priv->client->elf_ctx,
|
|
sym_tab_in->exe_id,
|
|
sym_tab_buffer);
|
|
if (err)
|
|
goto free_mem;
|
|
|
|
err = copy_to_user((void __user *)sym_tab_in->tab.addr,
|
|
sym_tab_buffer,
|
|
tab_size);
|
|
|
|
free_mem:
|
|
kfree(sym_tab_buffer);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/* Maximum VPU print buffer size is 16M */
|
|
#define MAX_VPU_PRINT_BUFFER_SIZE (16 * (1 << 20))
|
|
static int pva_set_vpu_print_buffer_size(struct pva_private *priv, void *arg)
|
|
{
|
|
union nvpva_set_vpu_print_buffer_size_args *in_arg =
|
|
(union nvpva_set_vpu_print_buffer_size_args *)arg;
|
|
uint32_t buffer_size = in_arg->in.size;
|
|
struct device *dev = &priv->pva->aux_pdev->dev;
|
|
int err = 0;
|
|
|
|
if (buffer_size > MAX_VPU_PRINT_BUFFER_SIZE) {
|
|
dev_err(&priv->pva->pdev->dev,
|
|
"requested VPU print buffer too large: %u > %u\n",
|
|
buffer_size, MAX_VPU_PRINT_BUFFER_SIZE);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&priv->queue->list_lock);
|
|
if (!list_empty(&priv->queue->tasklist)) {
|
|
dev_err(&priv->pva->pdev->dev,
|
|
"can't set VPU print buffer size when there's unfinished tasks\n");
|
|
err = -EAGAIN;
|
|
goto unlock;
|
|
}
|
|
|
|
if (priv->vpu_print_buffer != NULL) {
|
|
pva_free_cb(dev, priv->vpu_print_buffer);
|
|
priv->vpu_print_buffer = NULL;
|
|
}
|
|
|
|
if (buffer_size == 0)
|
|
goto unlock;
|
|
|
|
priv->vpu_print_buffer = pva_alloc_cb(dev, buffer_size);
|
|
|
|
if (IS_ERR(priv->vpu_print_buffer)) {
|
|
err = PTR_ERR(priv->vpu_print_buffer);
|
|
priv->vpu_print_buffer = NULL;
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock(&priv->queue->list_lock);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static ssize_t pva_read_cb(struct pva_cb *cb, u8 __user *buffer,
|
|
size_t buffer_size)
|
|
{
|
|
const u32 tail = cb->tail;
|
|
const u32 head = *cb->head_va;
|
|
const u32 size = cb->size;
|
|
ssize_t ret = 0;
|
|
u32 transfer1_size;
|
|
u32 transfer2_size;
|
|
|
|
/*
|
|
* Check if overflow happened, and if so, report it.
|
|
*/
|
|
if (*cb->err_va != 0) {
|
|
pr_warn("pva: VPU print buffer overflowed!\n");
|
|
ret = -ENOSPC;
|
|
goto out;
|
|
}
|
|
|
|
transfer1_size = CIRC_CNT_TO_END(head, tail, size);
|
|
if (transfer1_size <= buffer_size) {
|
|
buffer_size -= transfer1_size;
|
|
} else {
|
|
transfer1_size = buffer_size;
|
|
buffer_size = 0;
|
|
}
|
|
|
|
transfer2_size =
|
|
CIRC_CNT(head, tail, size) - CIRC_CNT_TO_END(head, tail, size);
|
|
if (transfer2_size <= buffer_size) {
|
|
buffer_size -= transfer2_size;
|
|
} else {
|
|
transfer2_size = buffer_size;
|
|
buffer_size = 0;
|
|
}
|
|
|
|
if (transfer1_size > 0) {
|
|
unsigned long failed_count;
|
|
|
|
failed_count = copy_to_user(buffer, cb->buffer_va + tail,
|
|
transfer1_size);
|
|
if (failed_count > 0) {
|
|
pr_err("pva: VPU print buffer: write to user buffer 1 failed\n");
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (transfer2_size > 0) {
|
|
unsigned long failed_count;
|
|
|
|
failed_count = copy_to_user(&buffer[transfer1_size],
|
|
cb->buffer_va, transfer2_size);
|
|
if (failed_count > 0) {
|
|
pr_err("pva: VPU print buffer: write to user buffer 2 failed\n");
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
cb->tail =
|
|
(cb->tail + transfer1_size + transfer2_size) & (cb->size - 1);
|
|
|
|
/*
|
|
* Update tail so that firmware knows the content is consumed; Memory
|
|
* barrier is needed here because the update should only be visible to
|
|
* firmware after the content is read.
|
|
*/
|
|
mb();
|
|
*cb->tail_va = cb->tail;
|
|
ret = transfer1_size + transfer2_size;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static long pva_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct pva_private *priv = file->private_data;
|
|
u8 buf[NVPVA_IOCTL_MAX_SIZE] __aligned(sizeof(u64));
|
|
int err = 0;
|
|
int err2 = 0;
|
|
|
|
nvpva_dbg_fn(priv->pva, "");
|
|
|
|
if ((_IOC_TYPE(cmd) != NVPVA_IOCTL_MAGIC) ||
|
|
(_IOC_NR(cmd) == 0) ||
|
|
(_IOC_NR(cmd) > NVPVA_IOCTL_NUMBER_MAX) ||
|
|
(_IOC_SIZE(cmd) > sizeof(buf)))
|
|
return -ENOIOCTLCMD;
|
|
|
|
if (_IOC_DIR(cmd) & _IOC_WRITE) {
|
|
if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd))) {
|
|
dev_err(&priv->pva->pdev->dev,
|
|
"failed copy ioctl buffer from user; size: %u",
|
|
_IOC_SIZE(cmd));
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
switch (cmd) {
|
|
case NVPVA_IOCTL_GET_SYMBOL_ID:
|
|
err = pva_get_symbol_id(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_GET_SYM_TAB:
|
|
err = pva_get_symtab(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_REGISTER_VPU_EXEC:
|
|
err = pva_register_vpu_exec(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_UNREGISTER_VPU_EXEC:
|
|
err = pva_unregister_vpu_exec(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_PIN:
|
|
err = pva_pin(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_PIN_EX:
|
|
err = pva_pin_ex(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_UNPIN:
|
|
err = pva_unpin(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_SUBMIT:
|
|
err = pva_submit(priv, buf);
|
|
break;
|
|
case NVPVA_IOCTL_SET_VPU_PRINT_BUFFER_SIZE:
|
|
err = pva_set_vpu_print_buffer_size(priv, buf);
|
|
break;
|
|
default:
|
|
err2 = -ENOIOCTLCMD;
|
|
break;
|
|
}
|
|
|
|
if ((err2 == 0) && (_IOC_DIR(cmd) & _IOC_READ))
|
|
err2 = copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd));
|
|
|
|
err = (err == 0) ? err2 : err;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int pva_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct nvhost_device_data *pdata = container_of(
|
|
inode->i_cdev, struct nvhost_device_data, ctrl_cdev);
|
|
struct platform_device *pdev = pdata->pdev;
|
|
struct pva *pva = pdata->private_data;
|
|
struct pva_private *priv;
|
|
int err = 0;
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (priv == NULL) {
|
|
err = -ENOMEM;
|
|
goto err_alloc_priv;
|
|
}
|
|
|
|
file->private_data = priv;
|
|
priv->pva = pva;
|
|
priv->client = nvpva_client_context_alloc(pdev, pva, current->pid);
|
|
if (priv->client == NULL) {
|
|
err = -ENOMEM;
|
|
dev_err(&pdev->dev, "failed to allocate client context");
|
|
goto err_alloc_context;
|
|
}
|
|
|
|
priv->queue = nvpva_queue_alloc(pva->pool,
|
|
priv->client->cntxt_dev,
|
|
MAX_PVA_TASK_COUNT_PER_QUEUE);
|
|
|
|
if (IS_ERR(priv->queue)) {
|
|
err = PTR_ERR(priv->queue);
|
|
goto err_alloc_queue;
|
|
}
|
|
|
|
sema_init(&priv->queue->task_pool_sem, MAX_PVA_TASK_COUNT_PER_QUEUE);
|
|
err = nvhost_module_busy(pva->pdev);
|
|
if (err < 0) {
|
|
dev_err(&pva->pdev->dev, "error in powering up pva %d",
|
|
err);
|
|
goto err_device_busy;
|
|
}
|
|
|
|
return nonseekable_open(inode, file);
|
|
|
|
err_device_busy:
|
|
nvpva_queue_put(priv->queue);
|
|
err_alloc_queue:
|
|
nvpva_client_context_put(priv->client);
|
|
err_alloc_context:
|
|
kfree(priv);
|
|
err_alloc_priv:
|
|
return err;
|
|
}
|
|
|
|
static void pva_queue_flush(struct pva *pva, struct nvpva_queue *queue)
|
|
{
|
|
u32 flags = PVA_CMD_INT_ON_ERR | PVA_CMD_INT_ON_COMPLETE;
|
|
struct pva_cmd_status_regs status = {};
|
|
struct pva_cmd_s cmd = {};
|
|
int err = 0;
|
|
u32 nregs;
|
|
|
|
nregs = pva_cmd_abort_task(&cmd, queue->id, flags);
|
|
err = nvhost_module_busy(pva->pdev);
|
|
if (err < 0) {
|
|
dev_err(&pva->pdev->dev, "error in powering up pva %d",
|
|
err);
|
|
goto err_out;
|
|
}
|
|
|
|
err = pva->version_config->submit_cmd_sync(pva, &cmd, nregs, queue->id,
|
|
&status);
|
|
nvhost_module_idle(pva->pdev);
|
|
if (err < 0) {
|
|
dev_err(&pva->pdev->dev, "failed to issue FW abort command: %d",
|
|
err);
|
|
goto err_out;
|
|
}
|
|
/* Ensure that response is valid */
|
|
if (status.error != PVA_ERR_NO_ERROR) {
|
|
dev_err(&pva->pdev->dev, "PVA FW Abort rejected: %d",
|
|
status.error);
|
|
}
|
|
|
|
err_out:
|
|
return;
|
|
}
|
|
|
|
static int pva_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct pva_private *priv = file->private_data;
|
|
bool queue_empty;
|
|
int i;
|
|
|
|
flush_workqueue(priv->pva->task_status_workqueue);
|
|
mutex_lock(&priv->queue->list_lock);
|
|
queue_empty = list_empty(&priv->queue->tasklist);
|
|
mutex_unlock(&priv->queue->list_lock);
|
|
if (!queue_empty) {
|
|
/* Cancel remaining tasks */
|
|
nvpva_dbg_info(priv->pva, "cancel remaining tasks");
|
|
pva_queue_flush(priv->pva, priv->queue);
|
|
}
|
|
|
|
/* make sure all tasks have been finished */
|
|
for (i = 0; i < MAX_PVA_TASK_COUNT_PER_QUEUE; i++) {
|
|
if (down_killable(&priv->queue->task_pool_sem) != 0) {
|
|
nvpva_err(
|
|
&priv->pva->pdev->dev,
|
|
"interrupted while waiting %d tasks\n",
|
|
MAX_PVA_TASK_COUNT_PER_QUEUE - i);
|
|
pva_abort(priv->pva);
|
|
break;
|
|
}
|
|
}
|
|
|
|
nvhost_module_idle(priv->pva->pdev);
|
|
|
|
/* Release reference to client */
|
|
nvpva_client_context_put(priv->client);
|
|
|
|
/*
|
|
* Release handle to the queue (on-going tasks have their
|
|
* own references to the queue
|
|
*/
|
|
nvpva_queue_put(priv->queue);
|
|
|
|
/* Free VPU print buffer if allocated */
|
|
if (priv->vpu_print_buffer != NULL) {
|
|
pva_free_cb(&priv->pva->pdev->dev, priv->vpu_print_buffer);
|
|
priv->vpu_print_buffer = NULL;
|
|
}
|
|
|
|
/* Finally, release the private data */
|
|
kfree(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t pva_read_vpu_print_buffer(struct file *file,
|
|
char __user *user_buffer,
|
|
size_t buffer_size, loff_t *off)
|
|
{
|
|
struct pva_private *priv = file->private_data;
|
|
ssize_t ret;
|
|
|
|
mutex_lock(&priv->queue->list_lock);
|
|
|
|
if (priv->vpu_print_buffer != NULL) {
|
|
ret = pva_read_cb(priv->vpu_print_buffer, user_buffer,
|
|
buffer_size);
|
|
} else {
|
|
pr_warn("pva: VPU print buffer size needs to be specified\n");
|
|
ret = -EIO;
|
|
}
|
|
|
|
mutex_unlock(&priv->queue->list_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
const struct file_operations tegra_pva_ctrl_ops = {
|
|
.owner = THIS_MODULE,
|
|
#if defined(NV_NO_LLSEEK_PRESENT)
|
|
.llseek = no_llseek,
|
|
#endif
|
|
.unlocked_ioctl = pva_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = pva_ioctl,
|
|
#endif
|
|
.open = pva_open,
|
|
.release = pva_release,
|
|
.read = pva_read_vpu_print_buffer,
|
|
};
|