aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/video/tegra/host/nvhost_job.c
blob: df7a62d689bc5e7a586ccf4c1a5a1f258f754346 (plain) (tree)


















































































































































































































































































































































                                                                              
/*
 * drivers/video/tegra/host/nvhost_job.c
 *
 * Tegra Graphics Host Job
 *
 * Copyright (c) 2010-2012, NVIDIA Corporation.
 *
 * 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/kref.h>
#include <linux/err.h>
#include <linux/vmalloc.h>
#include <mach/nvmap.h>
#include "nvhost_channel.h"
#include "nvhost_job.h"
#include "dev.h"

/* Magic to use to fill freed handle slots */
#define BAD_MAGIC 0xdeadbeef

static int job_size(struct nvhost_submit_hdr_ext *hdr)
{
	int num_pins = hdr ? (hdr->num_relocs + hdr->num_cmdbufs)*2 : 0;
	int num_waitchks = hdr ? hdr->num_waitchks : 0;

	return sizeof(struct nvhost_job)
			+ num_pins * sizeof(struct nvmap_pinarray_elem)
			+ num_pins * sizeof(struct nvmap_handle *)
			+ num_waitchks * sizeof(struct nvhost_waitchk);
}

static int gather_size(int num_cmdbufs)
{
	return num_cmdbufs * sizeof(struct nvhost_channel_gather);
}

static void free_gathers(struct nvhost_job *job)
{
	if (job->gathers) {
		nvmap_munmap(job->gather_mem, job->gathers);
		job->gathers = NULL;
	}
	if (job->gather_mem) {
		nvmap_free(job->nvmap, job->gather_mem);
		job->gather_mem = NULL;
	}
}

static int alloc_gathers(struct nvhost_job *job,
		int num_cmdbufs)
{
	int err = 0;

	job->gather_mem = NULL;
	job->gathers = NULL;
	job->gather_mem_size = 0;

	if (num_cmdbufs) {
		/* Allocate memory */
		job->gather_mem = nvmap_alloc(job->nvmap,
				gather_size(num_cmdbufs),
				32, NVMAP_HANDLE_CACHEABLE, 0);
		if (IS_ERR_OR_NULL(job->gather_mem)) {
			err = PTR_ERR(job->gather_mem);
			job->gather_mem = NULL;
			goto error;
		}
		job->gather_mem_size = gather_size(num_cmdbufs);

		/* Map memory to kernel */
		job->gathers = nvmap_mmap(job->gather_mem);
		if (IS_ERR_OR_NULL(job->gathers)) {
			err = PTR_ERR(job->gathers);
			job->gathers = NULL;
			goto error;
		}
	}

	return 0;

error:
	free_gathers(job);
	return err;
}

static int realloc_gathers(struct nvhost_job *oldjob,
		struct nvhost_job *newjob,
		int num_cmdbufs)
{
	int err = 0;

	/* Check if we can reuse gather buffer */
	if (oldjob->gather_mem_size < gather_size(num_cmdbufs)
			|| oldjob->nvmap != newjob->nvmap) {
		free_gathers(oldjob);
		err = alloc_gathers(newjob, num_cmdbufs);
	} else {
		newjob->gather_mem = oldjob->gather_mem;
		newjob->gathers = oldjob->gathers;
		newjob->gather_mem_size = oldjob->gather_mem_size;

		oldjob->gather_mem = NULL;
		oldjob->gathers = NULL;
		oldjob->gather_mem_size = 0;
	}
	return err;
}

static void init_fields(struct nvhost_job *job,
		struct nvhost_submit_hdr_ext *hdr,
		int priority, int clientid)
{
	int num_pins = hdr ? (hdr->num_relocs + hdr->num_cmdbufs)*2 : 0;
	int num_waitchks = hdr ? hdr->num_waitchks : 0;
	void *mem = job;

	/* First init state to zero */
	job->num_gathers = 0;
	job->num_pins = 0;
	job->num_unpins = 0;
	job->num_waitchk = 0;
	job->waitchk_mask = 0;
	job->syncpt_id = 0;
	job->syncpt_incrs = 0;
	job->syncpt_end = 0;
	job->priority = priority;
	job->clientid = clientid;
	job->null_kickoff = false;
	job->first_get = 0;
	job->num_slots = 0;

	/* Redistribute memory to the structs */
	mem += sizeof(struct nvhost_job);
	if (num_pins) {
		job->pinarray = mem;
		mem += num_pins * sizeof(struct nvmap_pinarray_elem);
		job->unpins = mem;
		mem += num_pins * sizeof(struct nvmap_handle *);
	} else {
		job->pinarray = NULL;
		job->unpins = NULL;
	}

	job->waitchk = num_waitchks ? mem : NULL;

	/* Copy information from header */
	if (hdr) {
		job->waitchk_mask = hdr->waitchk_mask;
		job->syncpt_id = hdr->syncpt_id;
		job->syncpt_incrs = hdr->syncpt_incrs;
	}
}

struct nvhost_job *nvhost_job_alloc(struct nvhost_channel *ch,
		struct nvhost_hwctx *hwctx,
		struct nvhost_submit_hdr_ext *hdr,
		struct nvmap_client *nvmap,
		int priority,
		int clientid)
{
	struct nvhost_job *job = NULL;
	int num_cmdbufs = hdr ? hdr->num_cmdbufs : 0;
	int err = 0;

	job = vzalloc(job_size(hdr));
	if (!job)
		goto error;

	kref_init(&job->ref);
	job->ch = ch;
	job->hwctx = hwctx;
	if (hwctx)
		hwctx->h->get(hwctx);
	job->nvmap = nvmap ? nvmap_client_get(nvmap) : NULL;

	err = alloc_gathers(job, num_cmdbufs);
	if (err)
		goto error;

	init_fields(job, hdr, priority, clientid);

	return job;

error:
	if (job)
		nvhost_job_put(job);
	return NULL;
}

struct nvhost_job *nvhost_job_realloc(
		struct nvhost_job *oldjob,
		struct nvhost_hwctx *hwctx,
		struct nvhost_submit_hdr_ext *hdr,
		struct nvmap_client *nvmap,
		int priority, int clientid)
{
	struct nvhost_job *newjob = NULL;
	int num_cmdbufs = hdr ? hdr->num_cmdbufs : 0;
	int err = 0;

	newjob = vzalloc(job_size(hdr));
	if (!newjob)
		goto error;
	kref_init(&newjob->ref);
	newjob->ch = oldjob->ch;
	newjob->hwctx = hwctx;
	if (hwctx)
		newjob->hwctx->h->get(newjob->hwctx);
	newjob->timeout = oldjob->timeout;
	newjob->nvmap = nvmap ? nvmap_client_get(nvmap) : NULL;

	err = realloc_gathers(oldjob, newjob, num_cmdbufs);
	if (err)
		goto error;

	nvhost_job_put(oldjob);

	init_fields(newjob, hdr, priority, clientid);

	return newjob;

error:
	if (newjob)
		nvhost_job_put(newjob);
	if (oldjob)
		nvhost_job_put(oldjob);
	return NULL;
}

void nvhost_job_get(struct nvhost_job *job)
{
	kref_get(&job->ref);
}

static void job_free(struct kref *ref)
{
	struct nvhost_job *job = container_of(ref, struct nvhost_job, ref);

	if (job->hwctxref)
		job->hwctxref->h->put(job->hwctxref);
	if (job->hwctx)
		job->hwctx->h->put(job->hwctx);
	if (job->gathers)
		nvmap_munmap(job->gather_mem, job->gathers);
	if (job->gather_mem)
		nvmap_free(job->nvmap, job->gather_mem);
	if (job->nvmap)
		nvmap_client_put(job->nvmap);
	vfree(job);
}

/* Acquire reference to a hardware context. Used for keeping saved contexts in
 * memory. */
void nvhost_job_get_hwctx(struct nvhost_job *job, struct nvhost_hwctx *hwctx)
{
	BUG_ON(job->hwctxref);

	job->hwctxref = hwctx;
	hwctx->h->get(hwctx);
}

void nvhost_job_put(struct nvhost_job *job)
{
	kref_put(&job->ref, job_free);
}

void nvhost_job_add_gather(struct nvhost_job *job,
		u32 mem_id, u32 words, u32 offset)
{
	struct nvmap_pinarray_elem *pin;
	struct nvhost_channel_gather *cur_gather =
			&job->gathers[job->num_gathers];

	pin = &job->pinarray[job->num_pins++];
	pin->patch_mem = (u32)nvmap_ref_to_handle(job->gather_mem);
	pin->patch_offset = (void *)&(cur_gather->mem) - (void *)job->gathers;
	pin->pin_mem = nvmap_convert_handle_u2k(mem_id);
	pin->pin_offset = offset;
	cur_gather->words = words;
	cur_gather->mem_id = mem_id;
	cur_gather->offset = offset;
	job->num_gathers += 1;
}

int nvhost_job_pin(struct nvhost_job *job)
{
	int err = 0;

	/* pin mem handles and patch physical addresses */
	job->num_unpins = nvmap_pin_array(job->nvmap,
				nvmap_ref_to_handle(job->gather_mem),
				job->pinarray, job->num_pins,
				job->unpins);
	if (job->num_unpins < 0)
		err = job->num_unpins;

	return err;
}

void nvhost_job_unpin(struct nvhost_job *job)
{
	nvmap_unpin_handles(job->nvmap, job->unpins,
			job->num_unpins);
	memset(job->unpins, BAD_MAGIC,
			job->num_unpins * sizeof(struct nvmap_handle *));
}

/**
 * Debug routine used to dump job entries
 */
void nvhost_job_dump(struct device *dev, struct nvhost_job *job)
{
	dev_dbg(dev, "    SYNCPT_ID   %d\n",
		job->syncpt_id);
	dev_dbg(dev, "    SYNCPT_VAL  %d\n",
		job->syncpt_end);
	dev_dbg(dev, "    FIRST_GET   0x%x\n",
		job->first_get);
	dev_dbg(dev, "    TIMEOUT     %d\n",
		job->timeout);
	dev_dbg(dev, "    CTX 0x%p\n",
		job->hwctx);
	dev_dbg(dev, "    NUM_SLOTS   %d\n",
		job->num_slots);
	dev_dbg(dev, "    NUM_HANDLES %d\n",
		job->num_unpins);
}