tegrakernel/kernel/nvidia/drivers/video/tegra/host/host1x/host1x_channel.c

432 lines
12 KiB
C

/*
* drivers/video/tegra/host/host1x/channel_host1x.c
*
* Tegra Graphics Host Channel
*
* Copyright (c) 2010-2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/slab.h>
#include <linux/version.h>
#include <trace/events/nvhost.h>
#include "nvhost_channel.h"
#include "nvhost_acm.h"
#include "nvhost_job.h"
#include "nvhost_sync.h"
#include "nvhost_intr.h"
#include "class_ids.h"
#include "debug.h"
#include "dev.h"
#define NVHOST_CHANNEL_LOW_PRIO_MAX_WAIT 50
static void submit_work_done_increment(struct nvhost_job *job)
{
struct nvhost_channel *ch = job->ch;
struct nvhost_syncpt *sp = &nvhost_get_host(ch->dev)->syncpt;
struct nvhost_device_data *pdata = platform_get_drvdata(ch->dev);
if (!pdata->push_work_done)
return;
/* make the last increment at job boundary. this will ensure
* that the user command buffer is no longer in use */
job->sp[0].fence = nvhost_syncpt_incr_max(sp, job->sp[0].id, 1);
nvhost_cdma_push(&ch->cdma, nvhost_opcode_imm_incr_syncpt(0,
job->sp[0].id), NVHOST_OPCODE_NOOP);
}
static void lock_device(struct nvhost_job *job, bool lock)
{
struct nvhost_channel *ch = job->ch;
struct nvhost_device_data *pdata = platform_get_drvdata(ch->dev);
u32 opcode = lock ?
nvhost_opcode_acquire_mlock(pdata->modulemutexes[0]) :
nvhost_opcode_release_mlock(pdata->modulemutexes[0]);
/* No need to do anything if we have a channel/engine */
if (pdata->resource_policy == RESOURCE_PER_DEVICE)
return;
/* If we have a hardware mlock, use it. */
if (pdata->modulemutexes[0]) {
nvhost_cdma_push(&ch->cdma, opcode, NVHOST_OPCODE_NOOP);
return;
}
if (lock) {
nvhost_cdma_push(&ch->cdma,
nvhost_opcode_setclass(NV_HOST1X_CLASS_ID,
host1x_uclass_wait_syncpt_r(), 1),
nvhost_class_host_wait_syncpt(
pdata->last_submit_syncpt_id,
pdata->last_submit_syncpt_value));
} else {
pdata->last_submit_syncpt_id = job->sp[0].id;
pdata->last_submit_syncpt_value = job->sp[0].fence;
}
}
static void push_wait(struct nvhost_cdma *cdma, unsigned int id,
unsigned int thresh)
{
/*
* Force serialization by inserting a host wait for the
* previous job to finish before this one can commence.
*
* NOTE! This cannot be packed because otherwise we might
* overwrite the RESTART opcode at the end of the push
* buffer.
*/
nvhost_cdma_push(cdma,
nvhost_opcode_setclass(NV_HOST1X_CLASS_ID,
host1x_uclass_load_syncpt_payload_32_r(), 1),
thresh);
nvhost_cdma_push(cdma,
nvhost_opcode_setclass(NV_HOST1X_CLASS_ID,
host1x_uclass_wait_syncpt_32_r(), 1),
id);
}
static void serialize(struct nvhost_job *job)
{
struct nvhost_channel *ch = job->ch;
struct nvhost_syncpt *sp = &nvhost_get_host(ch->dev)->syncpt;
struct nvhost_device_data *pdata = platform_get_drvdata(ch->dev);
int i;
if (!job->serialize && !pdata->serialize)
return;
for (i = 0; i < job->num_syncpts; ++i)
push_wait(&ch->cdma, job->sp[i].id,
nvhost_syncpt_read_max(sp, job->sp[i].id));
}
#ifdef CONFIG_TEGRA_GRHOST_SYNC
static void add_sync_waits(struct nvhost_channel *ch, int fd)
{
struct nvhost_master *host = nvhost_get_host(ch->dev);
struct nvhost_syncpt *sp = &host->syncpt;
struct sync_fence *fence;
struct sync_pt *pt;
int i;
u32 id, thresh;
if (fd < 0)
return;
fence = nvhost_sync_fdget(fd);
if (!fence)
return;
i = id = thresh = 0;
/* validate syncpt ids */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)
list_for_each_entry(pt, &fence->pt_list_head, pt_list) {
#else
for (i = 0; i < fence->num_fences; i++) {
pt = sync_pt_from_fence(fence->cbs[i].sync_pt);
#endif
id = nvhost_sync_pt_id(pt);
if (!id || !nvhost_syncpt_is_valid_hw_pt(sp, id)) {
sync_fence_put(fence);
return;
}
}
/*
* Force serialization by inserting a host wait for the
* previous job to finish before this one can commence.
*
* NOTE! This cannot be packed because otherwise we might
* overwrite the RESTART opcode at the end of the push
* buffer.
*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)
list_for_each_entry(pt, &fence->pt_list_head, pt_list) {
#else
for (i = 0; i < fence->num_fences; i++) {
pt = sync_pt_from_fence(fence->cbs[i].sync_pt);
#endif
id = nvhost_sync_pt_id(pt);
thresh = nvhost_sync_pt_thresh(pt);
if (nvhost_syncpt_is_expired(sp, id, thresh))
continue;
push_wait(&ch->cdma, id, thresh);
}
sync_fence_put(fence);
}
#else
static void add_sync_waits(struct nvhost_channel *ch, int fd)
{
(void)ch;
(void)fd;
}
#endif
static void push_waits(struct nvhost_job *job)
{
struct nvhost_device_data *pdata = platform_get_drvdata(job->ch->dev);
struct nvhost_syncpt *sp = &nvhost_get_host(job->ch->dev)->syncpt;
struct nvhost_channel *ch = job->ch;
int i;
for (i = 0; i < job->num_waitchk; i++) {
struct nvhost_waitchk *wait = &job->waitchk[i];
/* skip pushing waits if we allow them (map-at-open mode)
* and userspace wants to push a wait to some explicit
* position */
if (pdata->resource_policy == RESOURCE_PER_DEVICE && wait->mem)
continue;
/* Skip pushing wait if it has already been expired */
if (nvhost_syncpt_is_expired(sp, wait->syncpt_id,
wait->thresh))
continue;
push_wait(&ch->cdma, wait->syncpt_id, wait->thresh);
}
for (i = 0; i < job->num_gathers; i++)
add_sync_waits(job->ch, job->gathers[i].pre_fence);
}
static void submit_work(struct nvhost_job *job)
{
struct nvhost_device_data *pdata = platform_get_drvdata(job->ch->dev);
void *cpuva = NULL;
bool use_locking =
pdata->resource_policy == RESOURCE_PER_CHANNEL_INSTANCE;
u32 cur_class = 0;
int i;
/* make all waits in the beginning */
push_waits(job);
/* push user gathers */
for (i = 0 ; i < job->num_gathers; i++) {
struct nvhost_job_gather *g = &job->gathers[i];
u32 op1;
u32 op2;
/* handle class changing */
if (!cur_class || cur_class != g->class_id) {
/* first, release current class */
if (use_locking && cur_class &&
cur_class != NV_HOST1X_CLASS_ID) {
lock_device(job, false);
dev_warn(&job->ch->dev->dev, "%s changes out from engine class",
current->comm);
}
/* acquire lock of the new class */
if (use_locking && g->class_id != NV_HOST1X_CLASS_ID)
lock_device(job, true);
/* update current class */
nvhost_cdma_push(&job->ch->cdma,
nvhost_opcode_setclass(g->class_id, 0, 0),
NVHOST_OPCODE_NOOP);
cur_class = g->class_id;
/* initialize class context */
if (cur_class != NV_HOST1X_CLASS_ID &&
pdata->init_class_context)
pdata->init_class_context(
job->ch->dev, &job->ch->cdma);
}
op1 = nvhost_opcode_gather(g->words);
op2 = job->gathers[i].mem_base + g->offset;
if (nvhost_debug_trace_cmdbuf)
cpuva = dma_buf_vmap(g->buf);
nvhost_cdma_push_gather(&job->ch->cdma,
cpuva,
job->gathers[i].mem_base,
g->offset,
op1, op2);
if (cpuva)
dma_buf_vunmap(g->buf, cpuva);
}
/* wait all work to complete */
serialize(job);
/* make final increment */
submit_work_done_increment(job);
/* release the engine */
if (use_locking && cur_class && cur_class != NV_HOST1X_CLASS_ID)
lock_device(job, false);
}
static int host1x_channel_submit(struct nvhost_job *job)
{
struct nvhost_channel *ch = job->ch;
struct nvhost_syncpt *sp = &nvhost_get_host(job->ch->dev)->syncpt;
u32 prev_max = 0;
int err, i;
void *completed_waiters[job->num_syncpts];
memset(completed_waiters, 0, sizeof(void *) * job->num_syncpts);
/* Turn on the client module and host1x */
for (i = 0; i < job->num_syncpts; ++i) {
err = nvhost_module_busy(ch->dev);
if (err) {
nvhost_module_idle_mult(ch->dev, i);
nvhost_putchannel(ch, i);
return err;
}
nvhost_getchannel(ch);
}
/* before error checks, return current max */
prev_max = job->sp->fence = nvhost_syncpt_read_max(sp, job->sp->id);
/* get submit lock */
err = mutex_lock_interruptible(&ch->submitlock);
if (err) {
nvhost_module_idle_mult(ch->dev, job->num_syncpts);
nvhost_putchannel(ch, job->num_syncpts);
goto error;
}
for (i = 0; i < job->num_syncpts; ++i) {
completed_waiters[i] = nvhost_intr_alloc_waiter();
if (!completed_waiters[i]) {
nvhost_module_idle_mult(ch->dev, job->num_syncpts);
nvhost_putchannel(ch, job->num_syncpts);
mutex_unlock(&ch->submitlock);
err = -ENOMEM;
goto error;
}
if (nvhost_intr_has_pending_jobs(
&nvhost_get_host(ch->dev)->intr, job->sp[i].id, ch))
dev_warn(&ch->dev->dev,
"%s: cross-channel dependencies on syncpt %d\n",
__func__, job->sp[i].id);
}
/* begin a CDMA submit */
err = nvhost_cdma_begin(&ch->cdma, job);
if (err) {
nvhost_module_idle_mult(ch->dev, job->num_syncpts);
nvhost_putchannel(ch, job->num_syncpts);
mutex_unlock(&ch->submitlock);
goto error;
}
/* determine fences for all syncpoints */
for (i = 0; i < job->num_syncpts; ++i) {
u32 incrs = job->sp[i].incrs;
/* create a valid max for client managed syncpoints */
if (nvhost_syncpt_client_managed(sp, job->sp[i].id)) {
u32 min = nvhost_syncpt_read(sp, job->sp[i].id);
if (min)
dev_warn(&job->ch->dev->dev,
"converting an active unmanaged syncpoint %d to managed\n",
job->sp[i].id);
nvhost_syncpt_set_max(sp, job->sp[i].id, min);
nvhost_syncpt_set_manager(sp, job->sp[i].id, false);
}
job->sp[i].fence =
nvhost_syncpt_incr_max(sp, job->sp[i].id, incrs);
/* mark syncpoint used by this channel */
nvhost_syncpt_get_ref(sp, job->sp[i].id);
nvhost_syncpt_mark_used(sp, ch->chid, job->sp[i].id);
}
/* mark also client managed syncpoint used by this channel */
if (job->client_managed_syncpt)
nvhost_syncpt_mark_used(sp, ch->chid,
job->client_managed_syncpt);
/* push work to hardware */
submit_work(job);
/* end CDMA submit & stash pinned hMems into sync queue */
nvhost_cdma_end(&ch->cdma, job);
trace_nvhost_channel_submitted(ch->dev->name, prev_max,
job->sp->fence);
for (i = 0; i < job->num_syncpts; ++i) {
/* schedule a submit complete interrupt */
err = nvhost_intr_add_action(&nvhost_get_host(ch->dev)->intr,
job->sp[i].id, job->sp[i].fence,
NVHOST_INTR_ACTION_SUBMIT_COMPLETE, ch,
completed_waiters[i],
NULL);
WARN(err, "Failed to set submit complete interrupt");
}
mutex_unlock(&ch->submitlock);
return 0;
error:
for (i = 0; i < job->num_syncpts; ++i)
kfree(completed_waiters[i]);
return err;
}
#ifdef _hw_host1x04_channel_h_
static int t124_channel_init_gather_filter(struct platform_device *pdev,
struct nvhost_channel *ch)
{
struct nvhost_master *master = nvhost_get_host(pdev);
if (!nvhost_gather_filter_enabled(&master->syncpt))
return -EINVAL;
host1x_channel_writel(ch, host1x_channel_channelctrl_r(),
host1x_channel_channelctrl_kernel_filter_gbuffer_f(1));
return 0;
}
#endif
static int host1x_channel_init(struct nvhost_channel *ch,
struct nvhost_master *dev)
{
ch->aperture = host1x_channel_aperture(dev->aperture, ch->chid);
return 0;
}
static const struct nvhost_channel_ops host1x_channel_ops = {
.init = host1x_channel_init,
.submit = host1x_channel_submit,
#ifdef _hw_host1x04_channel_h_
.init_gather_filter = t124_channel_init_gather_filter,
#endif
};