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