Commit 6579324a authored by Terje Bergstrom's avatar Terje Bergstrom Committed by Thierry Reding

gpu: host1x: Add channel support

Add support for host1x client modules, and host1x channels to submit
work to the clients.
Signed-off-by: default avatarArto Merilainen <amerilainen@nvidia.com>
Signed-off-by: default avatarTerje Bergstrom <tbergstrom@nvidia.com>
Reviewed-by: default avatarThierry Reding <thierry.reding@avionic-design.de>
Tested-by: default avatarThierry Reding <thierry.reding@avionic-design.de>
Tested-by: default avatarErik Faye-Lund <kusmabite@gmail.com>
Signed-off-by: default avatarThierry Reding <thierry.reding@avionic-design.de>
parent 7ede0b0b
......@@ -8,3 +8,15 @@ config TEGRA_HOST1X
Tegra's graphics- and multimedia-related modules. The modules served
by host1x are referred to as clients. host1x includes some other
functionality, such as synchronization.
if TEGRA_HOST1X
config TEGRA_HOST1X_FIREWALL
bool "Enable HOST1X security firewall"
default y
help
Say yes if kernel should protect command streams from tampering.
If unsure, choose Y.
endif
......@@ -4,6 +4,9 @@ host1x-y = \
syncpt.o \
dev.o \
intr.o \
cdma.o \
channel.o \
job.o \
hw/host1x01.o
obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
/*
* Tegra host1x Command DMA
*
* Copyright (c) 2010-2013, 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 <asm/cacheflush.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/slab.h>
#include <trace/events/host1x.h>
#include "cdma.h"
#include "channel.h"
#include "dev.h"
#include "debug.h"
#include "host1x_bo.h"
#include "job.h"
/*
* push_buffer
*
* The push buffer is a circular array of words to be fetched by command DMA.
* Note that it works slightly differently to the sync queue; fence == pos
* means that the push buffer is full, not empty.
*/
#define HOST1X_PUSHBUFFER_SLOTS 512
/*
* Clean up push buffer resources
*/
static void host1x_pushbuffer_destroy(struct push_buffer *pb)
{
struct host1x_cdma *cdma = pb_to_cdma(pb);
struct host1x *host1x = cdma_to_host1x(cdma);
if (pb->phys != 0)
dma_free_writecombine(host1x->dev, pb->size_bytes + 4,
pb->mapped, pb->phys);
pb->mapped = NULL;
pb->phys = 0;
}
/*
* Init push buffer resources
*/
static int host1x_pushbuffer_init(struct push_buffer *pb)
{
struct host1x_cdma *cdma = pb_to_cdma(pb);
struct host1x *host1x = cdma_to_host1x(cdma);
pb->mapped = NULL;
pb->phys = 0;
pb->size_bytes = HOST1X_PUSHBUFFER_SLOTS * 8;
/* initialize buffer pointers */
pb->fence = pb->size_bytes - 8;
pb->pos = 0;
/* allocate and map pushbuffer memory */
pb->mapped = dma_alloc_writecombine(host1x->dev, pb->size_bytes + 4,
&pb->phys, GFP_KERNEL);
if (!pb->mapped)
goto fail;
host1x_hw_pushbuffer_init(host1x, pb);
return 0;
fail:
host1x_pushbuffer_destroy(pb);
return -ENOMEM;
}
/*
* Push two words to the push buffer
* Caller must ensure push buffer is not full
*/
static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2)
{
u32 pos = pb->pos;
u32 *p = (u32 *)((u32)pb->mapped + pos);
WARN_ON(pos == pb->fence);
*(p++) = op1;
*(p++) = op2;
pb->pos = (pos + 8) & (pb->size_bytes - 1);
}
/*
* Pop a number of two word slots from the push buffer
* Caller must ensure push buffer is not empty
*/
static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots)
{
/* Advance the next write position */
pb->fence = (pb->fence + slots * 8) & (pb->size_bytes - 1);
}
/*
* Return the number of two word slots free in the push buffer
*/
static u32 host1x_pushbuffer_space(struct push_buffer *pb)
{
return ((pb->fence - pb->pos) & (pb->size_bytes - 1)) / 8;
}
/*
* Sleep (if necessary) until the requested event happens
* - CDMA_EVENT_SYNC_QUEUE_EMPTY : sync queue is completely empty.
* - Returns 1
* - CDMA_EVENT_PUSH_BUFFER_SPACE : there is space in the push buffer
* - Return the amount of space (> 0)
* Must be called with the cdma lock held.
*/
unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma,
enum cdma_event event)
{
for (;;) {
unsigned int space;
if (event == CDMA_EVENT_SYNC_QUEUE_EMPTY)
space = list_empty(&cdma->sync_queue) ? 1 : 0;
else if (event == CDMA_EVENT_PUSH_BUFFER_SPACE) {
struct push_buffer *pb = &cdma->push_buffer;
space = host1x_pushbuffer_space(pb);
} else {
WARN_ON(1);
return -EINVAL;
}
if (space)
return space;
trace_host1x_wait_cdma(dev_name(cdma_to_channel(cdma)->dev),
event);
/* If somebody has managed to already start waiting, yield */
if (cdma->event != CDMA_EVENT_NONE) {
mutex_unlock(&cdma->lock);
schedule();
mutex_lock(&cdma->lock);
continue;
}
cdma->event = event;
mutex_unlock(&cdma->lock);
down(&cdma->sem);
mutex_lock(&cdma->lock);
}
return 0;
}
/*
* Start timer that tracks the time spent by the job.
* Must be called with the cdma lock held.
*/
static void cdma_start_timer_locked(struct host1x_cdma *cdma,
struct host1x_job *job)
{
struct host1x *host = cdma_to_host1x(cdma);
if (cdma->timeout.client) {
/* timer already started */
return;
}
cdma->timeout.client = job->client;
cdma->timeout.syncpt = host1x_syncpt_get(host, job->syncpt_id);
cdma->timeout.syncpt_val = job->syncpt_end;
cdma->timeout.start_ktime = ktime_get();
schedule_delayed_work(&cdma->timeout.wq,
msecs_to_jiffies(job->timeout));
}
/*
* Stop timer when a buffer submission completes.
* Must be called with the cdma lock held.
*/
static void stop_cdma_timer_locked(struct host1x_cdma *cdma)
{
cancel_delayed_work(&cdma->timeout.wq);
cdma->timeout.client = 0;
}
/*
* For all sync queue entries that have already finished according to the
* current sync point registers:
* - unpin & unref their mems
* - pop their push buffer slots
* - remove them from the sync queue
* This is normally called from the host code's worker thread, but can be
* called manually if necessary.
* Must be called with the cdma lock held.
*/
static void update_cdma_locked(struct host1x_cdma *cdma)
{
bool signal = false;
struct host1x *host1x = cdma_to_host1x(cdma);
struct host1x_job *job, *n;
/* If CDMA is stopped, queue is cleared and we can return */
if (!cdma->running)
return;
/*
* Walk the sync queue, reading the sync point registers as necessary,
* to consume as many sync queue entries as possible without blocking
*/
list_for_each_entry_safe(job, n, &cdma->sync_queue, list) {
struct host1x_syncpt *sp =
host1x_syncpt_get(host1x, job->syncpt_id);
/* Check whether this syncpt has completed, and bail if not */
if (!host1x_syncpt_is_expired(sp, job->syncpt_end)) {
/* Start timer on next pending syncpt */
if (job->timeout)
cdma_start_timer_locked(cdma, job);
break;
}
/* Cancel timeout, when a buffer completes */
if (cdma->timeout.client)
stop_cdma_timer_locked(cdma);
/* Unpin the memory */
host1x_job_unpin(job);
/* Pop push buffer slots */
if (job->num_slots) {
struct push_buffer *pb = &cdma->push_buffer;
host1x_pushbuffer_pop(pb, job->num_slots);
if (cdma->event == CDMA_EVENT_PUSH_BUFFER_SPACE)
signal = true;
}
list_del(&job->list);
host1x_job_put(job);
}
if (cdma->event == CDMA_EVENT_SYNC_QUEUE_EMPTY &&
list_empty(&cdma->sync_queue))
signal = true;
if (signal) {
cdma->event = CDMA_EVENT_NONE;
up(&cdma->sem);
}
}
void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
struct device *dev)
{
u32 restart_addr;
u32 syncpt_incrs;
struct host1x_job *job = NULL;
u32 syncpt_val;
struct host1x *host1x = cdma_to_host1x(cdma);
syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt);
dev_dbg(dev, "%s: starting cleanup (thresh %d)\n",
__func__, syncpt_val);
/*
* Move the sync_queue read pointer to the first entry that hasn't
* completed based on the current HW syncpt value. It's likely there
* won't be any (i.e. we're still at the head), but covers the case
* where a syncpt incr happens just prior/during the teardown.
*/
dev_dbg(dev, "%s: skip completed buffers still in sync_queue\n",
__func__);
list_for_each_entry(job, &cdma->sync_queue, list) {
if (syncpt_val < job->syncpt_end)
break;
host1x_job_dump(dev, job);
}
/*
* Walk the sync_queue, first incrementing with the CPU syncpts that
* are partially executed (the first buffer) or fully skipped while
* still in the current context (slots are also NOP-ed).
*
* At the point contexts are interleaved, syncpt increments must be
* done inline with the pushbuffer from a GATHER buffer to maintain
* the order (slots are modified to be a GATHER of syncpt incrs).
*
* Note: save in restart_addr the location where the timed out buffer
* started in the PB, so we can start the refetch from there (with the
* modified NOP-ed PB slots). This lets things appear to have completed
* properly for this buffer and resources are freed.
*/
dev_dbg(dev, "%s: perform CPU incr on pending same ctx buffers\n",
__func__);
if (!list_empty(&cdma->sync_queue))
restart_addr = job->first_get;
else
restart_addr = cdma->last_pos;
/* do CPU increments as long as this context continues */
list_for_each_entry_from(job, &cdma->sync_queue, list) {
/* different context, gets us out of this loop */
if (job->client != cdma->timeout.client)
break;
/* won't need a timeout when replayed */
job->timeout = 0;
syncpt_incrs = job->syncpt_end - syncpt_val;
dev_dbg(dev, "%s: CPU incr (%d)\n", __func__, syncpt_incrs);
host1x_job_dump(dev, job);
/* safe to use CPU to incr syncpts */
host1x_hw_cdma_timeout_cpu_incr(host1x, cdma, job->first_get,
syncpt_incrs, job->syncpt_end,
job->num_slots);
syncpt_val += syncpt_incrs;
}
/* The following sumbits from the same client may be dependent on the
* failed submit and therefore they may fail. Force a small timeout
* to make the queue cleanup faster */
list_for_each_entry_from(job, &cdma->sync_queue, list)
if (job->client == cdma->timeout.client)
job->timeout = min_t(unsigned int, job->timeout, 500);
dev_dbg(dev, "%s: finished sync_queue modification\n", __func__);
/* roll back DMAGET and start up channel again */
host1x_hw_cdma_resume(host1x, cdma, restart_addr);
}
/*
* Create a cdma
*/
int host1x_cdma_init(struct host1x_cdma *cdma)
{
int err;
mutex_init(&cdma->lock);
sema_init(&cdma->sem, 0);
INIT_LIST_HEAD(&cdma->sync_queue);
cdma->event = CDMA_EVENT_NONE;
cdma->running = false;
cdma->torndown = false;
err = host1x_pushbuffer_init(&cdma->push_buffer);
if (err)
return err;
return 0;
}
/*
* Destroy a cdma
*/
int host1x_cdma_deinit(struct host1x_cdma *cdma)
{
struct push_buffer *pb = &cdma->push_buffer;
struct host1x *host1x = cdma_to_host1x(cdma);
if (cdma->running) {
pr_warn("%s: CDMA still running\n", __func__);
return -EBUSY;
}
host1x_pushbuffer_destroy(pb);
host1x_hw_cdma_timeout_destroy(host1x, cdma);
return 0;
}
/*
* Begin a cdma submit
*/
int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job)
{
struct host1x *host1x = cdma_to_host1x(cdma);
mutex_lock(&cdma->lock);
if (job->timeout) {
/* init state on first submit with timeout value */
if (!cdma->timeout.initialized) {
int err;
err = host1x_hw_cdma_timeout_init(host1x, cdma,
job->syncpt_id);
if (err) {
mutex_unlock(&cdma->lock);
return err;
}
}
}
if (!cdma->running)
host1x_hw_cdma_start(host1x, cdma);
cdma->slots_free = 0;
cdma->slots_used = 0;
cdma->first_get = cdma->push_buffer.pos;
trace_host1x_cdma_begin(dev_name(job->channel->dev));
return 0;
}
/*
* Push two words into a push buffer slot
* Blocks as necessary if the push buffer is full.
*/
void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2)
{
struct host1x *host1x = cdma_to_host1x(cdma);
struct push_buffer *pb = &cdma->push_buffer;
u32 slots_free = cdma->slots_free;
if (slots_free == 0) {
host1x_hw_cdma_flush(host1x, cdma);
slots_free = host1x_cdma_wait_locked(cdma,
CDMA_EVENT_PUSH_BUFFER_SPACE);
}
cdma->slots_free = slots_free - 1;
cdma->slots_used++;
host1x_pushbuffer_push(pb, op1, op2);
}
/*
* End a cdma submit
* Kick off DMA, add job to the sync queue, and a number of slots to be freed
* from the pushbuffer. The handles for a submit must all be pinned at the same
* time, but they can be unpinned in smaller chunks.
*/
void host1x_cdma_end(struct host1x_cdma *cdma,
struct host1x_job *job)
{
struct host1x *host1x = cdma_to_host1x(cdma);
bool idle = list_empty(&cdma->sync_queue);
host1x_hw_cdma_flush(host1x, cdma);
job->first_get = cdma->first_get;
job->num_slots = cdma->slots_used;
host1x_job_get(job);
list_add_tail(&job->list, &cdma->sync_queue);
/* start timer on idle -> active transitions */
if (job->timeout && idle)
cdma_start_timer_locked(cdma, job);
trace_host1x_cdma_end(dev_name(job->channel->dev));
mutex_unlock(&cdma->lock);
}
/*
* Update cdma state according to current sync point values
*/
void host1x_cdma_update(struct host1x_cdma *cdma)
{
mutex_lock(&cdma->lock);
update_cdma_locked(cdma);
mutex_unlock(&cdma->lock);
}
/*
* Tegra host1x Command DMA
*
* Copyright (c) 2010-2013, 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/>.
*/
#ifndef __HOST1X_CDMA_H
#define __HOST1X_CDMA_H
#include <linux/sched.h>
#include <linux/semaphore.h>
#include <linux/list.h>
struct host1x_syncpt;
struct host1x_userctx_timeout;
struct host1x_job;
/*
* cdma
*
* This is in charge of a host command DMA channel.
* Sends ops to a push buffer, and takes responsibility for unpinning
* (& possibly freeing) of memory after those ops have completed.
* Producer:
* begin
* push - send ops to the push buffer
* end - start command DMA and enqueue handles to be unpinned
* Consumer:
* update - call to update sync queue and push buffer, unpin memory
*/
struct push_buffer {
u32 *mapped; /* mapped pushbuffer memory */
dma_addr_t phys; /* physical address of pushbuffer */
u32 fence; /* index we've written */
u32 pos; /* index to write to */
u32 size_bytes;
};
struct buffer_timeout {
struct delayed_work wq; /* work queue */
bool initialized; /* timer one-time setup flag */
struct host1x_syncpt *syncpt; /* buffer completion syncpt */
u32 syncpt_val; /* syncpt value when completed */
ktime_t start_ktime; /* starting time */
/* context timeout information */
int client;
};
enum cdma_event {
CDMA_EVENT_NONE, /* not waiting for any event */
CDMA_EVENT_SYNC_QUEUE_EMPTY, /* wait for empty sync queue */
CDMA_EVENT_PUSH_BUFFER_SPACE /* wait for space in push buffer */
};
struct host1x_cdma {
struct mutex lock; /* controls access to shared state */
struct semaphore sem; /* signalled when event occurs */
enum cdma_event event; /* event that sem is waiting for */
unsigned int slots_used; /* pb slots used in current submit */
unsigned int slots_free; /* pb slots free in current submit */
unsigned int first_get; /* DMAGET value, where submit begins */
unsigned int last_pos; /* last value written to DMAPUT */
struct push_buffer push_buffer; /* channel's push buffer */
struct list_head sync_queue; /* job queue */
struct buffer_timeout timeout; /* channel's timeout state/wq */
bool running;
bool torndown;
};
#define cdma_to_channel(cdma) container_of(cdma, struct host1x_channel, cdma)
#define cdma_to_host1x(cdma) dev_get_drvdata(cdma_to_channel(cdma)->dev->parent)
#define pb_to_cdma(pb) container_of(pb, struct host1x_cdma, push_buffer)
int host1x_cdma_init(struct host1x_cdma *cdma);
int host1x_cdma_deinit(struct host1x_cdma *cdma);
void host1x_cdma_stop(struct host1x_cdma *cdma);
int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job);
void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2);
void host1x_cdma_end(struct host1x_cdma *cdma, struct host1x_job *job);
void host1x_cdma_update(struct host1x_cdma *cdma);
void host1x_cdma_peek(struct host1x_cdma *cdma, u32 dmaget, int slot,
u32 *out);
unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma,
enum cdma_event event);
void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
struct device *dev);
#endif
/*
* Tegra host1x Channel
*
* Copyright (c) 2010-2013, 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/module.h>
#include "channel.h"
#include "dev.h"
#include "job.h"
/* Constructor for the host1x device list */
int host1x_channel_list_init(struct host1x *host)
{
INIT_LIST_HEAD(&host->chlist.list);
mutex_init(&host->chlist_mutex);
if (host->info->nb_channels > BITS_PER_LONG) {
WARN(1, "host1x hardware has more channels than supported by the driver\n");
return -ENOSYS;
}
return 0;
}
int host1x_job_submit(struct host1x_job *job)
{
struct host1x *host = dev_get_drvdata(job->channel->dev->parent);
return host1x_hw_channel_submit(host, job);
}
struct host1x_channel *host1x_channel_get(struct host1x_channel *channel)
{
int err = 0;
mutex_lock(&channel->reflock);
if (channel->refcount == 0)
err = host1x_cdma_init(&channel->cdma);
if (!err)
channel->refcount++;
mutex_unlock(&channel->reflock);
return err ? NULL : channel;
}
void host1x_channel_put(struct host1x_channel *channel)
{
mutex_lock(&channel->reflock);
if (channel->refcount == 1) {
struct host1x *host = dev_get_drvdata(channel->dev->parent);
host1x_hw_cdma_stop(host, &channel->cdma);
host1x_cdma_deinit(&channel->cdma);
}
channel->refcount--;
mutex_unlock(&channel->reflock);
}
struct host1x_channel *host1x_channel_request(struct device *dev)
{
struct host1x *host = dev_get_drvdata(dev->parent);
int max_channels = host->info->nb_channels;
struct host1x_channel *channel = NULL;
int index, err;
mutex_lock(&host->chlist_mutex);
index = find_first_zero_bit(&host->allocated_channels, max_channels);
if (index >= max_channels)
goto fail;
channel = kzalloc(sizeof(*channel), GFP_KERNEL);
if (!channel)
goto fail;
err = host1x_hw_channel_init(host, channel, index);
if (err < 0)
goto fail;
/* Link device to host1x_channel */
channel->dev = dev;
/* Add to channel list */
list_add_tail(&channel->list, &host->chlist.list);
host->allocated_channels |= BIT(index);
mutex_unlock(&host->chlist_mutex);
return channel;
fail:
dev_err(dev, "failed to init channel\n");
kfree(channel);
mutex_unlock(&host->chlist_mutex);
return NULL;
}
void host1x_channel_free(struct host1x_channel *channel)
{
struct host1x *host = dev_get_drvdata(channel->dev->parent);
host->allocated_channels &= ~BIT(channel->id);
list_del(&channel->list);
kfree(channel);
}
/*
* Tegra host1x Channel
*
* Copyright (c) 2010-2013, 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/>.
*/
#ifndef __HOST1X_CHANNEL_H
#define __HOST1X_CHANNEL_H
#include <linux/io.h>
#include "cdma.h"
struct host1x;
struct host1x_channel {
struct list_head list;
unsigned int refcount;
unsigned int id;
struct mutex reflock;
struct mutex submitlock;
void __iomem *regs;
struct device *dev;
struct host1x_cdma cdma;
};
/* channel list operations */
int host1x_channel_list_init(struct host1x *host);
struct host1x_channel *host1x_channel_request(struct device *dev);
void host1x_channel_free(struct host1x_channel *channel);
struct host1x_channel *host1x_channel_get(struct host1x_channel *channel);
void host1x_channel_put(struct host1x_channel *channel);
int host1x_job_submit(struct host1x_job *job);
#define host1x_for_each_channel(host, channel) \
list_for_each_entry(channel, &host->chlist.list, list)
#endif
......@@ -29,6 +29,7 @@
#include "dev.h"
#include "intr.h"
#include "channel.h"
#include "hw/host1x01.h"
void host1x_sync_writel(struct host1x *host1x, u32 v, u32 r)
......@@ -45,6 +46,16 @@ u32 host1x_sync_readl(struct host1x *host1x, u32 r)
return readl(sync_regs + r);
}
void host1x_ch_writel(struct host1x_channel *ch, u32 v, u32 r)
{
writel(v, ch->regs + r);
}
u32 host1x_ch_readl(struct host1x_channel *ch, u32 r)
{
return readl(ch->regs + r);
}
static const struct host1x_info host1x01_info = {
.nb_channels = 8,
.nb_pts = 32,
......@@ -112,6 +123,12 @@ static int host1x_probe(struct platform_device *pdev)
return err;
}
err = host1x_channel_list_init(host);
if (err) {
dev_err(&pdev->dev, "failed to initialize channel list\n");
return err;
}
err = clk_prepare_enable(host->clk);
if (err < 0) {
dev_err(&pdev->dev, "failed to enable clock\n");
......
......@@ -20,10 +20,39 @@
#include <linux/platform_device.h>
#include <linux/device.h>
#include "channel.h"
#include "syncpt.h"
#include "intr.h"
#include "cdma.h"
#include "job.h"
struct host1x_syncpt;
struct host1x_channel;
struct host1x_cdma;
struct host1x_job;
struct push_buffer;
struct host1x_channel_ops {
int (*init)(struct host1x_channel *channel, struct host1x *host,
unsigned int id);
int (*submit)(struct host1x_job *job);
};
struct host1x_cdma_ops {
void (*start)(struct host1x_cdma *cdma);
void (*stop)(struct host1x_cdma *cdma);
void (*flush)(struct host1x_cdma *cdma);
int (*timeout_init)(struct host1x_cdma *cdma, u32 syncpt_id);
void (*timeout_destroy)(struct host1x_cdma *cdma);
void (*freeze)(struct host1x_cdma *cdma);
void (*resume)(struct host1x_cdma *cdma, u32 getptr);
void (*timeout_cpu_incr)(struct host1x_cdma *cdma, u32 getptr,
u32 syncpt_incrs, u32 syncval, u32 nr_slots);
};
struct host1x_pushbuffer_ops {
void (*init)(struct push_buffer *pb);
};
struct host1x_syncpt_ops {
void (*restore)(struct host1x_syncpt *syncpt);
......@@ -68,11 +97,22 @@ struct host1x {
const struct host1x_syncpt_ops *syncpt_op;
const struct host1x_intr_ops *intr_op;
const struct host1x_channel_ops *channel_op;
const struct host1x_cdma_ops *cdma_op;
const struct host1x_pushbuffer_ops *cdma_pb_op;
struct host1x_syncpt *nop_sp;
struct mutex chlist_mutex;
struct host1x_channel chlist;
unsigned long allocated_channels;
unsigned int num_allocated_channels;
};
void host1x_sync_writel(struct host1x *host1x, u32 r, u32 v);
u32 host1x_sync_readl(struct host1x *host1x, u32 r);
void host1x_ch_writel(struct host1x_channel *ch, u32 r, u32 v);
u32 host1x_ch_readl(struct host1x_channel *ch, u32 r);
static inline void host1x_hw_syncpt_restore(struct host1x *host,
struct host1x_syncpt *sp)
......@@ -144,4 +184,77 @@ static inline int host1x_hw_intr_free_syncpt_irq(struct host1x *host)
{
return host->intr_op->free_syncpt_irq(host);
}
static inline int host1x_hw_channel_init(struct host1x *host,
struct host1x_channel *channel,
int chid)
{
return host->channel_op->init(channel, host, chid);
}
static inline int host1x_hw_channel_submit(struct host1x *host,
struct host1x_job *job)
{
return host->channel_op->submit(job);
}
static inline void host1x_hw_cdma_start(struct host1x *host,
struct host1x_cdma *cdma)
{
host->cdma_op->start(cdma);
}
static inline void host1x_hw_cdma_stop(struct host1x *host,
struct host1x_cdma *cdma)
{
host->cdma_op->stop(cdma);
}
static inline void host1x_hw_cdma_flush(struct host1x *host,
struct host1x_cdma *cdma)
{
host->cdma_op->flush(cdma);
}
static inline int host1x_hw_cdma_timeout_init(struct host1x *host,
struct host1x_cdma *cdma,
u32 syncpt_id)
{
return host->cdma_op->timeout_init(cdma, syncpt_id);
}
static inline void host1x_hw_cdma_timeout_destroy(struct host1x *host,
struct host1x_cdma *cdma)
{
host->cdma_op->timeout_destroy(cdma);
}
static inline void host1x_hw_cdma_freeze(struct host1x *host,
struct host1x_cdma *cdma)
{
host->cdma_op->freeze(cdma);
}
static inline void host1x_hw_cdma_resume(struct host1x *host,
struct host1x_cdma *cdma, u32 getptr)
{
host->cdma_op->resume(cdma, getptr);
}
static inline void host1x_hw_cdma_timeout_cpu_incr(struct host1x *host,
struct host1x_cdma *cdma,
u32 getptr,
u32 syncpt_incrs,
u32 syncval, u32 nr_slots)
{
host->cdma_op->timeout_cpu_incr(cdma, getptr, syncpt_incrs, syncval,
nr_slots);
}
static inline void host1x_hw_pushbuffer_init(struct host1x *host,
struct push_buffer *pb)
{
host->cdma_pb_op->init(pb);
}
#endif
/*
* Tegra host1x driver
*
* Copyright (c) 2009-2013, NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __LINUX_HOST1X_H
#define __LINUX_HOST1X_H
enum host1x_class {
HOST1X_CLASS_HOST1X = 0x1
};
#endif
/*
* Tegra host1x Memory Management Abstraction header
*
* Copyright (c) 2012-2013, 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/>.
*/
#ifndef _HOST1X_BO_H
#define _HOST1X_BO_H
struct host1x_bo;
struct host1x_bo_ops {
struct host1x_bo *(*get)(struct host1x_bo *bo);
void (*put)(struct host1x_bo *bo);
dma_addr_t (*pin)(struct host1x_bo *bo, struct sg_table **sgt);
void (*unpin)(struct host1x_bo *bo, struct sg_table *sgt);
void *(*mmap)(struct host1x_bo *bo);
void (*munmap)(struct host1x_bo *bo, void *addr);
void *(*kmap)(struct host1x_bo *bo, unsigned int pagenum);
void (*kunmap)(struct host1x_bo *bo, unsigned int pagenum, void *addr);
};
struct host1x_bo {
const struct host1x_bo_ops *ops;
};
static inline void host1x_bo_init(struct host1x_bo *bo,
const struct host1x_bo_ops *ops)
{
bo->ops = ops;
}
static inline struct host1x_bo *host1x_bo_get(struct host1x_bo *bo)
{
return bo->ops->get(bo);
}
static inline void host1x_bo_put(struct host1x_bo *bo)
{
bo->ops->put(bo);
}
static inline dma_addr_t host1x_bo_pin(struct host1x_bo *bo,
struct sg_table **sgt)
{
return bo->ops->pin(bo, sgt);
}
static inline void host1x_bo_unpin(struct host1x_bo *bo, struct sg_table *sgt)
{
bo->ops->unpin(bo, sgt);
}
static inline void *host1x_bo_mmap(struct host1x_bo *bo)
{
return bo->ops->mmap(bo);
}
static inline void host1x_bo_munmap(struct host1x_bo *bo, void *addr)
{
bo->ops->munmap(bo, addr);
}
static inline void *host1x_bo_kmap(struct host1x_bo *bo, unsigned int pagenum)
{
return bo->ops->kmap(bo, pagenum);
}
static inline void host1x_bo_kunmap(struct host1x_bo *bo,
unsigned int pagenum, void *addr)
{
bo->ops->kunmap(bo, pagenum, addr);
}
#endif
/*
* Tegra host1x Command DMA
*
* Copyright (c) 2010-2013, 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/scatterlist.h>
#include <linux/dma-mapping.h>
#include "cdma.h"
#include "channel.h"
#include "dev.h"
#include "debug.h"
/*
* Put the restart at the end of pushbuffer memor
*/
static void push_buffer_init(struct push_buffer *pb)
{
*(pb->mapped + (pb->size_bytes >> 2)) = host1x_opcode_restart(0);
}
/*
* Increment timedout buffer's syncpt via CPU.
*/
static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
u32 syncpt_incrs, u32 syncval, u32 nr_slots)
{
struct host1x *host1x = cdma_to_host1x(cdma);
struct push_buffer *pb = &cdma->push_buffer;
u32 i;
for (i = 0; i < syncpt_incrs; i++)
host1x_syncpt_cpu_incr(cdma->timeout.syncpt);
/* after CPU incr, ensure shadow is up to date */
host1x_syncpt_load(cdma->timeout.syncpt);
/* NOP all the PB slots */
while (nr_slots--) {
u32 *p = (u32 *)((u32)pb->mapped + getptr);
*(p++) = HOST1X_OPCODE_NOP;
*(p++) = HOST1X_OPCODE_NOP;
dev_dbg(host1x->dev, "%s: NOP at 0x%x\n", __func__,
pb->phys + getptr);
getptr = (getptr + 8) & (pb->size_bytes - 1);
}
wmb();
}
/*
* Start channel DMA
*/
static void cdma_start(struct host1x_cdma *cdma)
{
struct host1x_channel *ch = cdma_to_channel(cdma);
if (cdma->running)
return;
cdma->last_pos = cdma->push_buffer.pos;
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
HOST1X_CHANNEL_DMACTRL);
/* set base, put and end pointer */
host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT);
host1x_ch_writel(ch, cdma->push_buffer.phys +
cdma->push_buffer.size_bytes + 4,
HOST1X_CHANNEL_DMAEND);
/* reset GET */
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP |
HOST1X_CHANNEL_DMACTRL_DMAGETRST |
HOST1X_CHANNEL_DMACTRL_DMAINITGET,
HOST1X_CHANNEL_DMACTRL);
/* start the command DMA */
host1x_ch_writel(ch, 0, HOST1X_CHANNEL_DMACTRL);
cdma->running = true;
}
/*
* Similar to cdma_start(), but rather than starting from an idle
* state (where DMA GET is set to DMA PUT), on a timeout we restore
* DMA GET from an explicit value (so DMA may again be pending).
*/
static void cdma_timeout_restart(struct host1x_cdma *cdma, u32 getptr)
{
struct host1x *host1x = cdma_to_host1x(cdma);
struct host1x_channel *ch = cdma_to_channel(cdma);
if (cdma->running)
return;
cdma->last_pos = cdma->push_buffer.pos;
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
HOST1X_CHANNEL_DMACTRL);
/* set base, end pointer (all of memory) */
host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, cdma->push_buffer.phys +
cdma->push_buffer.size_bytes,
HOST1X_CHANNEL_DMAEND);
/* set GET, by loading the value in PUT (then reset GET) */
host1x_ch_writel(ch, getptr, HOST1X_CHANNEL_DMAPUT);
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP |
HOST1X_CHANNEL_DMACTRL_DMAGETRST |
HOST1X_CHANNEL_DMACTRL_DMAINITGET,
HOST1X_CHANNEL_DMACTRL);
dev_dbg(host1x->dev,
"%s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x\n", __func__,
host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET),
host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT),
cdma->last_pos);
/* deassert GET reset and set PUT */
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
HOST1X_CHANNEL_DMACTRL);
host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT);
/* start the command DMA */
host1x_ch_writel(ch, 0, HOST1X_CHANNEL_DMACTRL);
cdma->running = true;
}
/*
* Kick channel DMA into action by writing its PUT offset (if it has changed)
*/
static void cdma_flush(struct host1x_cdma *cdma)
{
struct host1x_channel *ch = cdma_to_channel(cdma);
if (cdma->push_buffer.pos != cdma->last_pos) {
host1x_ch_writel(ch, cdma->push_buffer.pos,
HOST1X_CHANNEL_DMAPUT);
cdma->last_pos = cdma->push_buffer.pos;
}
}
static void cdma_stop(struct host1x_cdma *cdma)
{
struct host1x_channel *ch = cdma_to_channel(cdma);
mutex_lock(&cdma->lock);
if (cdma->running) {
host1x_cdma_wait_locked(cdma, CDMA_EVENT_SYNC_QUEUE_EMPTY);
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
HOST1X_CHANNEL_DMACTRL);
cdma->running = false;
}
mutex_unlock(&cdma->lock);
}
/*
* Stops both channel's command processor and CDMA immediately.
* Also, tears down the channel and resets corresponding module.
*/
static void cdma_freeze(struct host1x_cdma *cdma)
{
struct host1x *host = cdma_to_host1x(cdma);
struct host1x_channel *ch = cdma_to_channel(cdma);
u32 cmdproc_stop;
if (cdma->torndown && !cdma->running) {
dev_warn(host->dev, "Already torn down\n");
return;
}
dev_dbg(host->dev, "freezing channel (id %d)\n", ch->id);
cmdproc_stop = host1x_sync_readl(host, HOST1X_SYNC_CMDPROC_STOP);
cmdproc_stop |= BIT(ch->id);
host1x_sync_writel(host, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP);
dev_dbg(host->dev, "%s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x\n",
__func__, host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET),
host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT),
cdma->last_pos);
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
HOST1X_CHANNEL_DMACTRL);
host1x_sync_writel(host, BIT(ch->id), HOST1X_SYNC_CH_TEARDOWN);
cdma->running = false;
cdma->torndown = true;
}
static void cdma_resume(struct host1x_cdma *cdma, u32 getptr)
{
struct host1x *host1x = cdma_to_host1x(cdma);
struct host1x_channel *ch = cdma_to_channel(cdma);
u32 cmdproc_stop;
dev_dbg(host1x->dev,
"resuming channel (id %d, DMAGET restart = 0x%x)\n",
ch->id, getptr);
cmdproc_stop = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP);
cmdproc_stop &= ~(BIT(ch->id));
host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP);
cdma->torndown = false;
cdma_timeout_restart(cdma, getptr);
}
/*
* If this timeout fires, it indicates the current sync_queue entry has
* exceeded its TTL and the userctx should be timed out and remaining
* submits already issued cleaned up (future submits return an error).
*/
static void cdma_timeout_handler(struct work_struct *work)
{
struct host1x_cdma *cdma;
struct host1x *host1x;
struct host1x_channel *ch;
u32 syncpt_val;
u32 prev_cmdproc, cmdproc_stop;
cdma = container_of(to_delayed_work(work), struct host1x_cdma,
timeout.wq);
host1x = cdma_to_host1x(cdma);
ch = cdma_to_channel(cdma);
mutex_lock(&cdma->lock);
if (!cdma->timeout.client) {
dev_dbg(host1x->dev,
"cdma_timeout: expired, but has no clientid\n");
mutex_unlock(&cdma->lock);
return;
}
/* stop processing to get a clean snapshot */
prev_cmdproc = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP);
cmdproc_stop = prev_cmdproc | BIT(ch->id);
host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP);
dev_dbg(host1x->dev, "cdma_timeout: cmdproc was 0x%x is 0x%x\n",
prev_cmdproc, cmdproc_stop);
syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt);
/* has buffer actually completed? */
if ((s32)(syncpt_val - cdma->timeout.syncpt_val) >= 0) {
dev_dbg(host1x->dev,
"cdma_timeout: expired, but buffer had completed\n");
/* restore */
cmdproc_stop = prev_cmdproc & ~(BIT(ch->id));
host1x_sync_writel(host1x, cmdproc_stop,
HOST1X_SYNC_CMDPROC_STOP);
mutex_unlock(&cdma->lock);
return;
}
dev_warn(host1x->dev, "%s: timeout: %d (%s), HW thresh %d, done %d\n",
__func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name,
syncpt_val, cdma->timeout.syncpt_val);
/* stop HW, resetting channel/module */
host1x_hw_cdma_freeze(host1x, cdma);
host1x_cdma_update_sync_queue(cdma, ch->dev);
mutex_unlock(&cdma->lock);
}
/*
* Init timeout resources
*/
static int cdma_timeout_init(struct host1x_cdma *cdma, u32 syncpt_id)
{
INIT_DELAYED_WORK(&cdma->timeout.wq, cdma_timeout_handler);
cdma->timeout.initialized = true;
return 0;
}
/*
* Clean up timeout resources
*/
static void cdma_timeout_destroy(struct host1x_cdma *cdma)
{
if (cdma->timeout.initialized)
cancel_delayed_work(&cdma->timeout.wq);
cdma->timeout.initialized = false;
}
static const struct host1x_cdma_ops host1x_cdma_ops = {
.start = cdma_start,
.stop = cdma_stop,
.flush = cdma_flush,
.timeout_init = cdma_timeout_init,
.timeout_destroy = cdma_timeout_destroy,
.freeze = cdma_freeze,
.resume = cdma_resume,
.timeout_cpu_incr = cdma_timeout_cpu_incr,
};
static const struct host1x_pushbuffer_ops host1x_pushbuffer_ops = {
.init = push_buffer_init,
};
/*
* Tegra host1x Channel
*
* Copyright (c) 2010-2013, 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 <trace/events/host1x.h>
#include "host1x.h"
#include "host1x_bo.h"
#include "channel.h"
#include "dev.h"
#include "intr.h"
#include "job.h"
#define HOST1X_CHANNEL_SIZE 16384
#define TRACE_MAX_LENGTH 128U
static void submit_gathers(struct host1x_job *job)
{
struct host1x_cdma *cdma = &job->channel->cdma;
unsigned int i;
for (i = 0; i < job->num_gathers; i++) {
struct host1x_job_gather *g = &job->gathers[i];
u32 op1 = host1x_opcode_gather(g->words);
u32 op2 = g->base + g->offset;
host1x_cdma_push(cdma, op1, op2);
}
}
static int channel_submit(struct host1x_job *job)
{
struct host1x_channel *ch = job->channel;
struct host1x_syncpt *sp;
u32 user_syncpt_incrs = job->syncpt_incrs;
u32 prev_max = 0;
u32 syncval;
int err;
struct host1x_waitlist *completed_waiter = NULL;
struct host1x *host = dev_get_drvdata(ch->dev->parent);
sp = host->syncpt + job->syncpt_id;
trace_host1x_channel_submit(dev_name(ch->dev),
job->num_gathers, job->num_relocs,
job->num_waitchk, job->syncpt_id,
job->syncpt_incrs);
/* before error checks, return current max */
prev_max = job->syncpt_end = host1x_syncpt_read_max(sp);
/* get submit lock */
err = mutex_lock_interruptible(&ch->submitlock);
if (err)
goto error;
completed_waiter = kzalloc(sizeof(*completed_waiter), GFP_KERNEL);
if (!completed_waiter) {
mutex_unlock(&ch->submitlock);
err = -ENOMEM;
goto error;
}
/* begin a CDMA submit */
err = host1x_cdma_begin(&ch->cdma, job);
if (err) {
mutex_unlock(&ch->submitlock);
goto error;
}
if (job->serialize) {
/*
* Force serialization by inserting a host wait for the
* previous job to finish before this one can commence.
*/
host1x_cdma_push(&ch->cdma,
host1x_opcode_setclass(HOST1X_CLASS_HOST1X,
host1x_uclass_wait_syncpt_r(), 1),
host1x_class_host_wait_syncpt(job->syncpt_id,
host1x_syncpt_read_max(sp)));
}
syncval = host1x_syncpt_incr_max(sp, user_syncpt_incrs);
job->syncpt_end = syncval;
/* add a setclass for modules that require it */
if (job->class)
host1x_cdma_push(&ch->cdma,
host1x_opcode_setclass(job->class, 0, 0),
HOST1X_OPCODE_NOP);
submit_gathers(job);
/* end CDMA submit & stash pinned hMems into sync queue */
host1x_cdma_end(&ch->cdma, job);
trace_host1x_channel_submitted(dev_name(ch->dev), prev_max, syncval);
/* schedule a submit complete interrupt */
err = host1x_intr_add_action(host, job->syncpt_id, syncval,
HOST1X_INTR_ACTION_SUBMIT_COMPLETE, ch,
completed_waiter, NULL);
completed_waiter = NULL;
WARN(err, "Failed to set submit complete interrupt");
mutex_unlock(&ch->submitlock);
return 0;
error:
kfree(completed_waiter);
return err;
}
static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev,
unsigned int index)
{
ch->id = index;
mutex_init(&ch->reflock);
mutex_init(&ch->submitlock);
ch->regs = dev->regs + index * HOST1X_CHANNEL_SIZE;
return 0;
}
static const struct host1x_channel_ops host1x_channel_ops = {
.init = host1x_channel_init,
.submit = channel_submit,
};
......@@ -21,6 +21,8 @@
#include "hw/host1x01_hardware.h"
/* include code */
#include "hw/cdma_hw.c"
#include "hw/channel_hw.c"
#include "hw/intr_hw.c"
#include "hw/syncpt_hw.c"
......@@ -28,6 +30,9 @@
int host1x01_init(struct host1x *host)
{
host->channel_op = &host1x_channel_ops;
host->cdma_op = &host1x_cdma_ops;
host->cdma_pb_op = &host1x_pushbuffer_ops;
host->syncpt_op = &host1x_syncpt_ops;
host->intr_op = &host1x_intr_ops;
......
......@@ -22,6 +22,122 @@
#include <linux/types.h>
#include <linux/bitops.h>
#include "hw_host1x01_channel.h"
#include "hw_host1x01_sync.h"
#include "hw_host1x01_uclass.h"
static inline u32 host1x_class_host_wait_syncpt(
unsigned indx, unsigned threshold)
{
return host1x_uclass_wait_syncpt_indx_f(indx)
| host1x_uclass_wait_syncpt_thresh_f(threshold);
}
static inline u32 host1x_class_host_load_syncpt_base(
unsigned indx, unsigned threshold)
{
return host1x_uclass_load_syncpt_base_base_indx_f(indx)
| host1x_uclass_load_syncpt_base_value_f(threshold);
}
static inline u32 host1x_class_host_wait_syncpt_base(
unsigned indx, unsigned base_indx, unsigned offset)
{
return host1x_uclass_wait_syncpt_base_indx_f(indx)
| host1x_uclass_wait_syncpt_base_base_indx_f(base_indx)
| host1x_uclass_wait_syncpt_base_offset_f(offset);
}
static inline u32 host1x_class_host_incr_syncpt_base(
unsigned base_indx, unsigned offset)
{
return host1x_uclass_incr_syncpt_base_base_indx_f(base_indx)
| host1x_uclass_incr_syncpt_base_offset_f(offset);
}
static inline u32 host1x_class_host_incr_syncpt(
unsigned cond, unsigned indx)
{
return host1x_uclass_incr_syncpt_cond_f(cond)
| host1x_uclass_incr_syncpt_indx_f(indx);
}
static inline u32 host1x_class_host_indoff_reg_write(
unsigned mod_id, unsigned offset, bool auto_inc)
{
u32 v = host1x_uclass_indoff_indbe_f(0xf)
| host1x_uclass_indoff_indmodid_f(mod_id)
| host1x_uclass_indoff_indroffset_f(offset);
if (auto_inc)
v |= host1x_uclass_indoff_autoinc_f(1);
return v;
}
static inline u32 host1x_class_host_indoff_reg_read(
unsigned mod_id, unsigned offset, bool auto_inc)
{
u32 v = host1x_uclass_indoff_indmodid_f(mod_id)
| host1x_uclass_indoff_indroffset_f(offset)
| host1x_uclass_indoff_rwn_read_v();
if (auto_inc)
v |= host1x_uclass_indoff_autoinc_f(1);
return v;
}
/* cdma opcodes */
static inline u32 host1x_opcode_setclass(
unsigned class_id, unsigned offset, unsigned mask)
{
return (0 << 28) | (offset << 16) | (class_id << 6) | mask;
}
static inline u32 host1x_opcode_incr(unsigned offset, unsigned count)
{
return (1 << 28) | (offset << 16) | count;
}
static inline u32 host1x_opcode_nonincr(unsigned offset, unsigned count)
{
return (2 << 28) | (offset << 16) | count;
}
static inline u32 host1x_opcode_mask(unsigned offset, unsigned mask)
{
return (3 << 28) | (offset << 16) | mask;
}
static inline u32 host1x_opcode_imm(unsigned offset, unsigned value)
{
return (4 << 28) | (offset << 16) | value;
}
static inline u32 host1x_opcode_imm_incr_syncpt(unsigned cond, unsigned indx)
{
return host1x_opcode_imm(host1x_uclass_incr_syncpt_r(),
host1x_class_host_incr_syncpt(cond, indx));
}
static inline u32 host1x_opcode_restart(unsigned address)
{
return (5 << 28) | (address >> 4);
}
static inline u32 host1x_opcode_gather(unsigned count)
{
return (6 << 28) | count;
}
static inline u32 host1x_opcode_gather_nonincr(unsigned offset, unsigned count)
{
return (6 << 28) | (offset << 16) | BIT(15) | count;
}
static inline u32 host1x_opcode_gather_incr(unsigned offset, unsigned count)
{
return (6 << 28) | (offset << 16) | BIT(15) | BIT(14) | count;
}
#define HOST1X_OPCODE_NOP host1x_opcode_nonincr(0, 0)
#endif
/*
* Copyright (c) 2012-2013, 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/>.
*
*/
/*
* Function naming determines intended use:
*
* <x>_r(void) : Returns the offset for register <x>.
*
* <x>_w(void) : Returns the word offset for word (4 byte) element <x>.
*
* <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits.
*
* <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted
* and masked to place it at field <y> of register <x>. This value
* can be |'d with others to produce a full register value for
* register <x>.
*
* <x>_<y>_m(void) : Returns a mask for field <y> of register <x>. This
* value can be ~'d and then &'d to clear the value of field <y> for
* register <x>.
*
* <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted
* to place it at field <y> of register <x>. This value can be |'d
* with others to produce a full register value for <x>.
*
* <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register
* <x> value 'r' after being shifted to place its LSB at bit 0.
* This value is suitable for direct comparison with other unshifted
* values appropriate for use in field <y> of register <x>.
*
* <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for
* field <y> of register <x>. This value is suitable for direct
* comparison with unshifted values appropriate for use in field <y>
* of register <x>.
*/
#ifndef __hw_host1x_channel_host1x_h__
#define __hw_host1x_channel_host1x_h__
static inline u32 host1x_channel_dmastart_r(void)
{
return 0x14;
}
#define HOST1X_CHANNEL_DMASTART \
host1x_channel_dmastart_r()
static inline u32 host1x_channel_dmaput_r(void)
{
return 0x18;
}
#define HOST1X_CHANNEL_DMAPUT \
host1x_channel_dmaput_r()
static inline u32 host1x_channel_dmaget_r(void)
{
return 0x1c;
}
#define HOST1X_CHANNEL_DMAGET \
host1x_channel_dmaget_r()
static inline u32 host1x_channel_dmaend_r(void)
{
return 0x20;
}
#define HOST1X_CHANNEL_DMAEND \
host1x_channel_dmaend_r()
static inline u32 host1x_channel_dmactrl_r(void)
{
return 0x24;
}
#define HOST1X_CHANNEL_DMACTRL \
host1x_channel_dmactrl_r()
static inline u32 host1x_channel_dmactrl_dmastop(void)
{
return 1 << 0;
}
#define HOST1X_CHANNEL_DMACTRL_DMASTOP \
host1x_channel_dmactrl_dmastop()
static inline u32 host1x_channel_dmactrl_dmagetrst(void)
{
return 1 << 1;
}
#define HOST1X_CHANNEL_DMACTRL_DMAGETRST \
host1x_channel_dmactrl_dmagetrst()
static inline u32 host1x_channel_dmactrl_dmainitget(void)
{
return 1 << 2;
}
#define HOST1X_CHANNEL_DMACTRL_DMAINITGET \
host1x_channel_dmactrl_dmainitget()
#endif
......@@ -77,6 +77,18 @@ static inline u32 host1x_sync_syncpt_thresh_int_enable_cpu0_r(unsigned int id)
}
#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(id) \
host1x_sync_syncpt_thresh_int_enable_cpu0_r(id)
static inline u32 host1x_sync_cmdproc_stop_r(void)
{
return 0xac;
}
#define HOST1X_SYNC_CMDPROC_STOP \
host1x_sync_cmdproc_stop_r()
static inline u32 host1x_sync_ch_teardown_r(void)
{
return 0xb0;
}
#define HOST1X_SYNC_CH_TEARDOWN \
host1x_sync_ch_teardown_r()
static inline u32 host1x_sync_usec_clk_r(void)
{
return 0x1a4;
......
/*
* Copyright (c) 2012-2013, 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/>.
*
*/
/*
* Function naming determines intended use:
*
* <x>_r(void) : Returns the offset for register <x>.
*
* <x>_w(void) : Returns the word offset for word (4 byte) element <x>.
*
* <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits.
*
* <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted
* and masked to place it at field <y> of register <x>. This value
* can be |'d with others to produce a full register value for
* register <x>.
*
* <x>_<y>_m(void) : Returns a mask for field <y> of register <x>. This
* value can be ~'d and then &'d to clear the value of field <y> for
* register <x>.
*
* <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted
* to place it at field <y> of register <x>. This value can be |'d
* with others to produce a full register value for <x>.
*
* <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register
* <x> value 'r' after being shifted to place its LSB at bit 0.
* This value is suitable for direct comparison with other unshifted
* values appropriate for use in field <y> of register <x>.
*
* <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for
* field <y> of register <x>. This value is suitable for direct
* comparison with unshifted values appropriate for use in field <y>
* of register <x>.
*/
#ifndef __hw_host1x_uclass_host1x_h__
#define __hw_host1x_uclass_host1x_h__
static inline u32 host1x_uclass_incr_syncpt_r(void)
{
return 0x0;
}
#define HOST1X_UCLASS_INCR_SYNCPT \
host1x_uclass_incr_syncpt_r()
static inline u32 host1x_uclass_incr_syncpt_cond_f(u32 v)
{
return (v & 0xff) << 8;
}
#define HOST1X_UCLASS_INCR_SYNCPT_COND_F(v) \
host1x_uclass_incr_syncpt_cond_f(v)
static inline u32 host1x_uclass_incr_syncpt_indx_f(u32 v)
{
return (v & 0xff) << 0;
}
#define HOST1X_UCLASS_INCR_SYNCPT_INDX_F(v) \
host1x_uclass_incr_syncpt_indx_f(v)
static inline u32 host1x_uclass_wait_syncpt_r(void)
{
return 0x8;
}
#define HOST1X_UCLASS_WAIT_SYNCPT \
host1x_uclass_wait_syncpt_r()
static inline u32 host1x_uclass_wait_syncpt_indx_f(u32 v)
{
return (v & 0xff) << 24;
}
#define HOST1X_UCLASS_WAIT_SYNCPT_INDX_F(v) \
host1x_uclass_wait_syncpt_indx_f(v)
static inline u32 host1x_uclass_wait_syncpt_thresh_f(u32 v)
{
return (v & 0xffffff) << 0;
}
#define HOST1X_UCLASS_WAIT_SYNCPT_THRESH_F(v) \
host1x_uclass_wait_syncpt_thresh_f(v)
static inline u32 host1x_uclass_wait_syncpt_base_indx_f(u32 v)
{
return (v & 0xff) << 24;
}
#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_INDX_F(v) \
host1x_uclass_wait_syncpt_base_indx_f(v)
static inline u32 host1x_uclass_wait_syncpt_base_base_indx_f(u32 v)
{
return (v & 0xff) << 16;
}
#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_BASE_INDX_F(v) \
host1x_uclass_wait_syncpt_base_base_indx_f(v)
static inline u32 host1x_uclass_wait_syncpt_base_offset_f(u32 v)
{
return (v & 0xffff) << 0;
}
#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_OFFSET_F(v) \
host1x_uclass_wait_syncpt_base_offset_f(v)
static inline u32 host1x_uclass_load_syncpt_base_base_indx_f(u32 v)
{
return (v & 0xff) << 24;
}
#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_BASE_INDX_F(v) \
host1x_uclass_load_syncpt_base_base_indx_f(v)
static inline u32 host1x_uclass_load_syncpt_base_value_f(u32 v)
{
return (v & 0xffffff) << 0;
}
#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(v) \
host1x_uclass_load_syncpt_base_value_f(v)
static inline u32 host1x_uclass_incr_syncpt_base_base_indx_f(u32 v)
{
return (v & 0xff) << 24;
}
#define HOST1X_UCLASS_INCR_SYNCPT_BASE_BASE_INDX_F(v) \
host1x_uclass_incr_syncpt_base_base_indx_f(v)
static inline u32 host1x_uclass_incr_syncpt_base_offset_f(u32 v)
{
return (v & 0xffffff) << 0;
}
#define HOST1X_UCLASS_INCR_SYNCPT_BASE_OFFSET_F(v) \
host1x_uclass_incr_syncpt_base_offset_f(v)
static inline u32 host1x_uclass_indoff_r(void)
{
return 0x2d;
}
#define HOST1X_UCLASS_INDOFF \
host1x_uclass_indoff_r()
static inline u32 host1x_uclass_indoff_indbe_f(u32 v)
{
return (v & 0xf) << 28;
}
#define HOST1X_UCLASS_INDOFF_INDBE_F(v) \
host1x_uclass_indoff_indbe_f(v)
static inline u32 host1x_uclass_indoff_autoinc_f(u32 v)
{
return (v & 0x1) << 27;
}
#define HOST1X_UCLASS_INDOFF_AUTOINC_F(v) \
host1x_uclass_indoff_autoinc_f(v)
static inline u32 host1x_uclass_indoff_indmodid_f(u32 v)
{
return (v & 0xff) << 18;
}
#define HOST1X_UCLASS_INDOFF_INDMODID_F(v) \
host1x_uclass_indoff_indmodid_f(v)
static inline u32 host1x_uclass_indoff_indroffset_f(u32 v)
{
return (v & 0xffff) << 2;
}
#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \
host1x_uclass_indoff_indroffset_f(v)
static inline u32 host1x_uclass_indoff_rwn_read_v(void)
{
return 1;
}
#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \
host1x_uclass_indoff_indroffset_f(v)
#endif
......@@ -93,10 +93,21 @@ static void syncpt_cpu_incr(struct host1x_syncpt *sp)
wmb();
}
/* remove a wait pointed to by patch_addr */
static int syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr)
{
u32 override = host1x_class_host_wait_syncpt(
HOST1X_SYNCPT_RESERVED, 0);
*((u32 *)patch_addr) = override;
return 0;
}
static const struct host1x_syncpt_ops host1x_syncpt_ops = {
.restore = syncpt_restore,
.restore_wait_base = syncpt_restore_wait_base,
.load_wait_base = syncpt_read_wait_base,
.load = syncpt_load,
.cpu_incr = syncpt_cpu_incr,
.patch_wait = syncpt_patch_wait,
};
......@@ -21,6 +21,8 @@
#include <linux/slab.h>
#include <linux/irq.h>
#include <trace/events/host1x.h>
#include "channel.h"
#include "dev.h"
#include "intr.h"
......@@ -66,7 +68,7 @@ static void remove_completed_waiters(struct list_head *head, u32 sync,
struct list_head completed[HOST1X_INTR_ACTION_COUNT])
{
struct list_head *dest;
struct host1x_waitlist *waiter, *next;
struct host1x_waitlist *waiter, *next, *prev;
list_for_each_entry_safe(waiter, next, head, list) {
if ((s32)(waiter->thresh - sync) > 0)
......@@ -74,6 +76,17 @@ static void remove_completed_waiters(struct list_head *head, u32 sync,
dest = completed + waiter->action;
/* consolidate submit cleanups */
if (waiter->action == HOST1X_INTR_ACTION_SUBMIT_COMPLETE &&
!list_empty(dest)) {
prev = list_entry(dest->prev,
struct host1x_waitlist, list);
if (prev->data == waiter->data) {
prev->count++;
dest = NULL;
}
}
/* PENDING->REMOVED or CANCELLED->HANDLED */
if (atomic_inc_return(&waiter->state) == WLS_HANDLED || !dest) {
list_del(&waiter->list);
......@@ -94,6 +107,18 @@ static void reset_threshold_interrupt(struct host1x *host,
host1x_hw_intr_enable_syncpt_intr(host, id);
}
static void action_submit_complete(struct host1x_waitlist *waiter)
{
struct host1x_channel *channel = waiter->data;
host1x_cdma_update(&channel->cdma);
/* Add nr_completed to trace */
trace_host1x_channel_submit_complete(dev_name(channel->dev),
waiter->count, waiter->thresh);
}
static void action_wakeup(struct host1x_waitlist *waiter)
{
wait_queue_head_t *wq = waiter->data;
......@@ -109,6 +134,7 @@ static void action_wakeup_interruptible(struct host1x_waitlist *waiter)
typedef void (*action_handler)(struct host1x_waitlist *waiter);
static action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = {
action_submit_complete,
action_wakeup,
action_wakeup_interruptible,
};
......
......@@ -25,6 +25,12 @@
struct host1x;
enum host1x_intr_action {
/*
* Perform cleanup after a submit has completed.
* 'data' points to a channel
*/
HOST1X_INTR_ACTION_SUBMIT_COMPLETE = 0,
/*
* Wake up a task.
* 'data' points to a wait_queue_head_t
......
/*
* Tegra host1x Job
*
* Copyright (c) 2010-2013, 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/dma-mapping.h>
#include <linux/err.h>
#include <linux/kref.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <trace/events/host1x.h>
#include "channel.h"
#include "dev.h"
#include "host1x_bo.h"
#include "job.h"
#include "syncpt.h"
struct host1x_job *host1x_job_alloc(struct host1x_channel *ch,
u32 num_cmdbufs, u32 num_relocs,
u32 num_waitchks)
{
struct host1x_job *job = NULL;
unsigned int num_unpins = num_cmdbufs + num_relocs;
u64 total;
void *mem;
/* Check that we're not going to overflow */
total = sizeof(struct host1x_job) +
num_relocs * sizeof(struct host1x_reloc) +
num_unpins * sizeof(struct host1x_job_unpin_data) +
num_waitchks * sizeof(struct host1x_waitchk) +
num_cmdbufs * sizeof(struct host1x_job_gather) +
num_unpins * sizeof(dma_addr_t) +
num_unpins * sizeof(u32 *);
if (total > ULONG_MAX)
return NULL;
mem = job = kzalloc(total, GFP_KERNEL);
if (!job)
return NULL;
kref_init(&job->ref);
job->channel = ch;
/* Redistribute memory to the structs */
mem += sizeof(struct host1x_job);
job->relocarray = num_relocs ? mem : NULL;
mem += num_relocs * sizeof(struct host1x_reloc);
job->unpins = num_unpins ? mem : NULL;
mem += num_unpins * sizeof(struct host1x_job_unpin_data);
job->waitchk = num_waitchks ? mem : NULL;
mem += num_waitchks * sizeof(struct host1x_waitchk);
job->gathers = num_cmdbufs ? mem : NULL;
mem += num_cmdbufs * sizeof(struct host1x_job_gather);
job->addr_phys = num_unpins ? mem : NULL;
job->reloc_addr_phys = job->addr_phys;
job->gather_addr_phys = &job->addr_phys[num_relocs];
return job;
}
struct host1x_job *host1x_job_get(struct host1x_job *job)
{
kref_get(&job->ref);
return job;
}
static void job_free(struct kref *ref)
{
struct host1x_job *job = container_of(ref, struct host1x_job, ref);
kfree(job);
}
void host1x_job_put(struct host1x_job *job)
{
kref_put(&job->ref, job_free);
}
void host1x_job_add_gather(struct host1x_job *job, struct host1x_bo *bo,
u32 words, u32 offset)
{
struct host1x_job_gather *cur_gather = &job->gathers[job->num_gathers];
cur_gather->words = words;
cur_gather->bo = bo;
cur_gather->offset = offset;
job->num_gathers++;
}
/*
* NULL an already satisfied WAIT_SYNCPT host method, by patching its
* args in the command stream. The method data is changed to reference
* a reserved (never given out or incr) HOST1X_SYNCPT_RESERVED syncpt
* with a matching threshold value of 0, so is guaranteed to be popped
* by the host HW.
*/
static void host1x_syncpt_patch_offset(struct host1x_syncpt *sp,
struct host1x_bo *h, u32 offset)
{
void *patch_addr = NULL;
/* patch the wait */
patch_addr = host1x_bo_kmap(h, offset >> PAGE_SHIFT);
if (patch_addr) {
host1x_syncpt_patch_wait(sp,
patch_addr + (offset & ~PAGE_MASK));
host1x_bo_kunmap(h, offset >> PAGE_SHIFT, patch_addr);
} else
pr_err("Could not map cmdbuf for wait check\n");
}
/*
* Check driver supplied waitchk structs for syncpt thresholds
* that have already been satisfied and NULL the comparison (to
* avoid a wrap condition in the HW).
*/
static int do_waitchks(struct host1x_job *job, struct host1x *host,
struct host1x_bo *patch)
{
int i;
/* compare syncpt vs wait threshold */
for (i = 0; i < job->num_waitchk; i++) {
struct host1x_waitchk *wait = &job->waitchk[i];
struct host1x_syncpt *sp =
host1x_syncpt_get(host, wait->syncpt_id);
/* validate syncpt id */
if (wait->syncpt_id > host1x_syncpt_nb_pts(host))
continue;
/* skip all other gathers */
if (patch != wait->bo)
continue;
trace_host1x_syncpt_wait_check(wait->bo, wait->offset,
wait->syncpt_id, wait->thresh,
host1x_syncpt_read_min(sp));
if (host1x_syncpt_is_expired(sp, wait->thresh)) {
dev_dbg(host->dev,
"drop WAIT id %d (%s) thresh 0x%x, min 0x%x\n",
wait->syncpt_id, sp->name, wait->thresh,
host1x_syncpt_read_min(sp));
host1x_syncpt_patch_offset(sp, patch, wait->offset);
}
wait->bo = NULL;
}
return 0;
}
static unsigned int pin_job(struct host1x_job *job)
{
unsigned int i;
job->num_unpins = 0;
for (i = 0; i < job->num_relocs; i++) {
struct host1x_reloc *reloc = &job->relocarray[i];
struct sg_table *sgt;
dma_addr_t phys_addr;
reloc->target = host1x_bo_get(reloc->target);
if (!reloc->target)
goto unpin;
phys_addr = host1x_bo_pin(reloc->target, &sgt);
if (!phys_addr)
goto unpin;
job->addr_phys[job->num_unpins] = phys_addr;
job->unpins[job->num_unpins].bo = reloc->target;
job->unpins[job->num_unpins].sgt = sgt;
job->num_unpins++;
}
for (i = 0; i < job->num_gathers; i++) {
struct host1x_job_gather *g = &job->gathers[i];
struct sg_table *sgt;
dma_addr_t phys_addr;
g->bo = host1x_bo_get(g->bo);
if (!g->bo)
goto unpin;
phys_addr = host1x_bo_pin(g->bo, &sgt);
if (!phys_addr)
goto unpin;
job->addr_phys[job->num_unpins] = phys_addr;
job->unpins[job->num_unpins].bo = g->bo;
job->unpins[job->num_unpins].sgt = sgt;
job->num_unpins++;
}
return job->num_unpins;
unpin:
host1x_job_unpin(job);
return 0;
}
static unsigned int do_relocs(struct host1x_job *job, struct host1x_bo *cmdbuf)
{
int i = 0;
u32 last_page = ~0;
void *cmdbuf_page_addr = NULL;
/* pin & patch the relocs for one gather */
while (i < job->num_relocs) {
struct host1x_reloc *reloc = &job->relocarray[i];
u32 reloc_addr = (job->reloc_addr_phys[i] +
reloc->target_offset) >> reloc->shift;
u32 *target;
/* skip all other gathers */
if (!(reloc->cmdbuf && cmdbuf == reloc->cmdbuf)) {
i++;
continue;
}
if (last_page != reloc->cmdbuf_offset >> PAGE_SHIFT) {
if (cmdbuf_page_addr)
host1x_bo_kunmap(cmdbuf, last_page,
cmdbuf_page_addr);
cmdbuf_page_addr = host1x_bo_kmap(cmdbuf,
reloc->cmdbuf_offset >> PAGE_SHIFT);
last_page = reloc->cmdbuf_offset >> PAGE_SHIFT;
if (unlikely(!cmdbuf_page_addr)) {
pr_err("Could not map cmdbuf for relocation\n");
return -ENOMEM;
}
}
target = cmdbuf_page_addr + (reloc->cmdbuf_offset & ~PAGE_MASK);
*target = reloc_addr;
/* mark this gather as handled */
reloc->cmdbuf = 0;
}
if (cmdbuf_page_addr)
host1x_bo_kunmap(cmdbuf, last_page, cmdbuf_page_addr);
return 0;
}
static int check_reloc(struct host1x_reloc *reloc, struct host1x_bo *cmdbuf,
unsigned int offset)
{
offset *= sizeof(u32);
if (reloc->cmdbuf != cmdbuf || reloc->cmdbuf_offset != offset)
return -EINVAL;
return 0;
}
struct host1x_firewall {
struct host1x_job *job;
struct device *dev;
unsigned int num_relocs;
struct host1x_reloc *reloc;
struct host1x_bo *cmdbuf_id;
unsigned int offset;
u32 words;
u32 class;
u32 reg;
u32 mask;
u32 count;
};
static int check_mask(struct host1x_firewall *fw)
{
u32 mask = fw->mask;
u32 reg = fw->reg;
while (mask) {
if (fw->words == 0)
return -EINVAL;
if (mask & 1) {
if (fw->job->is_addr_reg(fw->dev, fw->class, reg)) {
bool bad_reloc = check_reloc(fw->reloc,
fw->cmdbuf_id,
fw->offset);
if (!fw->num_relocs || bad_reloc)
return -EINVAL;
fw->reloc++;
fw->num_relocs--;
}
fw->words--;
fw->offset++;
}
mask >>= 1;
reg++;
}
return 0;
}
static int check_incr(struct host1x_firewall *fw)
{
u32 count = fw->count;
u32 reg = fw->reg;
while (fw) {
if (fw->words == 0)
return -EINVAL;
if (fw->job->is_addr_reg(fw->dev, fw->class, reg)) {
bool bad_reloc = check_reloc(fw->reloc, fw->cmdbuf_id,
fw->offset);
if (!fw->num_relocs || bad_reloc)
return -EINVAL;
fw->reloc++;
fw->num_relocs--;
}
reg++;
fw->words--;
fw->offset++;
count--;
}
return 0;
}
static int check_nonincr(struct host1x_firewall *fw)
{
int is_addr_reg = fw->job->is_addr_reg(fw->dev, fw->class, fw->reg);
u32 count = fw->count;
while (count) {
if (fw->words == 0)
return -EINVAL;
if (is_addr_reg) {
bool bad_reloc = check_reloc(fw->reloc, fw->cmdbuf_id,
fw->offset);
if (!fw->num_relocs || bad_reloc)
return -EINVAL;
fw->reloc++;
fw->num_relocs--;
}
fw->words--;
fw->offset++;
count--;
}
return 0;
}
static int validate(struct host1x_job *job, struct device *dev,
struct host1x_job_gather *g)
{
u32 *cmdbuf_base;
int err = 0;
struct host1x_firewall fw;
fw.job = job;
fw.dev = dev;
fw.reloc = job->relocarray;
fw.num_relocs = job->num_relocs;
fw.cmdbuf_id = g->bo;
fw.offset = 0;
fw.class = 0;
if (!job->is_addr_reg)
return 0;
cmdbuf_base = host1x_bo_mmap(g->bo);
if (!cmdbuf_base)
return -ENOMEM;
fw.words = g->words;
while (fw.words && !err) {
u32 word = cmdbuf_base[fw.offset];
u32 opcode = (word & 0xf0000000) >> 28;
fw.mask = 0;
fw.reg = 0;
fw.count = 0;
fw.words--;
fw.offset++;
switch (opcode) {
case 0:
fw.class = word >> 6 & 0x3ff;
fw.mask = word & 0x3f;
fw.reg = word >> 16 & 0xfff;
err = check_mask(&fw);
if (err)
goto out;
break;
case 1:
fw.reg = word >> 16 & 0xfff;
fw.count = word & 0xffff;
err = check_incr(&fw);
if (err)
goto out;
break;
case 2:
fw.reg = word >> 16 & 0xfff;
fw.count = word & 0xffff;
err = check_nonincr(&fw);
if (err)
goto out;
break;
case 3:
fw.mask = word & 0xffff;
fw.reg = word >> 16 & 0xfff;
err = check_mask(&fw);
if (err)
goto out;
break;
case 4:
case 5:
case 14:
break;
default:
err = -EINVAL;
break;
}
}
/* No relocs should remain at this point */
if (fw.num_relocs)
err = -EINVAL;
out:
host1x_bo_munmap(g->bo, cmdbuf_base);
return err;
}
static inline int copy_gathers(struct host1x_job *job, struct device *dev)
{
size_t size = 0;
size_t offset = 0;
int i;
for (i = 0; i < job->num_gathers; i++) {
struct host1x_job_gather *g = &job->gathers[i];
size += g->words * sizeof(u32);
}
job->gather_copy_mapped = dma_alloc_writecombine(dev, size,
&job->gather_copy,
GFP_KERNEL);
if (!job->gather_copy_mapped) {
int err = PTR_ERR(job->gather_copy_mapped);
job->gather_copy_mapped = NULL;
return err;
}
job->gather_copy_size = size;
for (i = 0; i < job->num_gathers; i++) {
struct host1x_job_gather *g = &job->gathers[i];
void *gather;
gather = host1x_bo_mmap(g->bo);
memcpy(job->gather_copy_mapped + offset, gather + g->offset,
g->words * sizeof(u32));
host1x_bo_munmap(g->bo, gather);
g->base = job->gather_copy;
g->offset = offset;
g->bo = NULL;
offset += g->words * sizeof(u32);
}
return 0;
}
int host1x_job_pin(struct host1x_job *job, struct device *dev)
{
int err;
unsigned int i, j;
struct host1x *host = dev_get_drvdata(dev->parent);
DECLARE_BITMAP(waitchk_mask, host1x_syncpt_nb_pts(host));
bitmap_zero(waitchk_mask, host1x_syncpt_nb_pts(host));
for (i = 0; i < job->num_waitchk; i++) {
u32 syncpt_id = job->waitchk[i].syncpt_id;
if (syncpt_id < host1x_syncpt_nb_pts(host))
set_bit(syncpt_id, waitchk_mask);
}
/* get current syncpt values for waitchk */
for_each_set_bit(i, waitchk_mask, host1x_syncpt_nb_pts(host))
host1x_syncpt_load(host->syncpt + i);
/* pin memory */
err = pin_job(job);
if (!err)
goto out;
/* patch gathers */
for (i = 0; i < job->num_gathers; i++) {
struct host1x_job_gather *g = &job->gathers[i];
/* process each gather mem only once */
if (g->handled)
continue;
g->base = job->gather_addr_phys[i];
for (j = 0; j < job->num_gathers; j++)
if (job->gathers[j].bo == g->bo)
job->gathers[j].handled = true;
err = 0;
if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL))
err = validate(job, dev, g);
if (err)
dev_err(dev, "Job invalid (err=%d)\n", err);
if (!err)
err = do_relocs(job, g->bo);
if (!err)
err = do_waitchks(job, host, g->bo);
if (err)
break;
}
if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL) && !err) {
err = copy_gathers(job, dev);
if (err) {
host1x_job_unpin(job);
return err;
}
}
out:
wmb();
return err;
}
void host1x_job_unpin(struct host1x_job *job)
{
unsigned int i;
for (i = 0; i < job->num_unpins; i++) {
struct host1x_job_unpin_data *unpin = &job->unpins[i];
host1x_bo_unpin(unpin->bo, unpin->sgt);
host1x_bo_put(unpin->bo);
}
job->num_unpins = 0;
if (job->gather_copy_size)
dma_free_writecombine(job->channel->dev, job->gather_copy_size,
job->gather_copy_mapped,
job->gather_copy);
}
/*
* Debug routine used to dump job entries
*/
void host1x_job_dump(struct device *dev, struct host1x_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, " NUM_SLOTS %d\n", job->num_slots);
dev_dbg(dev, " NUM_HANDLES %d\n", job->num_unpins);
}
/*
* Tegra host1x Job
*
* Copyright (c) 2011-2013, 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/>.
*/
#ifndef __HOST1X_JOB_H
#define __HOST1X_JOB_H
struct host1x_job_gather {
u32 words;
dma_addr_t base;
struct host1x_bo *bo;
int offset;
bool handled;
};
struct host1x_cmdbuf {
u32 handle;
u32 offset;
u32 words;
u32 pad;
};
struct host1x_reloc {
struct host1x_bo *cmdbuf;
u32 cmdbuf_offset;
struct host1x_bo *target;
u32 target_offset;
u32 shift;
u32 pad;
};
struct host1x_waitchk {
struct host1x_bo *bo;
u32 offset;
u32 syncpt_id;
u32 thresh;
};
struct host1x_job_unpin_data {
struct host1x_bo *bo;
struct sg_table *sgt;
};
/*
* Each submit is tracked as a host1x_job.
*/
struct host1x_job {
/* When refcount goes to zero, job can be freed */
struct kref ref;
/* List entry */
struct list_head list;
/* Channel where job is submitted to */
struct host1x_channel *channel;
u32 client;
/* Gathers and their memory */
struct host1x_job_gather *gathers;
unsigned int num_gathers;
/* Wait checks to be processed at submit time */
struct host1x_waitchk *waitchk;
unsigned int num_waitchk;
u32 waitchk_mask;
/* Array of handles to be pinned & unpinned */
struct host1x_reloc *relocarray;
unsigned int num_relocs;
struct host1x_job_unpin_data *unpins;
unsigned int num_unpins;
dma_addr_t *addr_phys;
dma_addr_t *gather_addr_phys;
dma_addr_t *reloc_addr_phys;
/* Sync point id, number of increments and end related to the submit */
u32 syncpt_id;
u32 syncpt_incrs;
u32 syncpt_end;
/* Maximum time to wait for this job */
unsigned int timeout;
/* Index and number of slots used in the push buffer */
unsigned int first_get;
unsigned int num_slots;
/* Copy of gathers */
size_t gather_copy_size;
dma_addr_t gather_copy;
u8 *gather_copy_mapped;
/* Check if register is marked as an address reg */
int (*is_addr_reg)(struct device *dev, u32 reg, u32 class);
/* Request a SETCLASS to this class */
u32 class;
/* Add a channel wait for previous ops to complete */
bool serialize;
};
/*
* Allocate memory for a job. Just enough memory will be allocated to
* accomodate the submit.
*/
struct host1x_job *host1x_job_alloc(struct host1x_channel *ch,
u32 num_cmdbufs, u32 num_relocs,
u32 num_waitchks);
/*
* Add a gather to a job.
*/
void host1x_job_add_gather(struct host1x_job *job, struct host1x_bo *mem_id,
u32 words, u32 offset);
/*
* Increment reference going to host1x_job.
*/
struct host1x_job *host1x_job_get(struct host1x_job *job);
/*
* Decrement reference job, free if goes to zero.
*/
void host1x_job_put(struct host1x_job *job);
/*
* Pin memory related to job. This handles relocation of addresses to the
* host1x address space. Handles both the gather memory and any other memory
* referred to from the gather buffers.
*
* Handles also patching out host waits that would wait for an expired sync
* point value.
*/
int host1x_job_pin(struct host1x_job *job, struct device *dev);
/*
* Unpin memory related to job.
*/
void host1x_job_unpin(struct host1x_job *job);
/*
* Dump contents of job to debug output.
*/
void host1x_job_dump(struct device *dev, struct host1x_job *job);
#endif
......@@ -300,6 +300,12 @@ bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh)
return (s32)(current_val - thresh) >= 0;
}
/* remove a wait pointed to by patch_addr */
int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr)
{
return host1x_hw_syncpt_patch_wait(sp->host, sp, patch_addr);
}
int host1x_syncpt_init(struct host1x *host)
{
struct host1x_syncpt *syncpt;
......@@ -319,6 +325,11 @@ int host1x_syncpt_init(struct host1x *host)
host1x_syncpt_restore(host);
/* Allocate sync point to use for clearing waits for expired fences */
host->nop_sp = _host1x_syncpt_alloc(host, NULL, 0);
if (!host->nop_sp)
return -ENOMEM;
return 0;
}
......
......@@ -27,6 +27,9 @@
struct host1x;
/* Reserved for replacing an expired wait with a NOP */
#define HOST1X_SYNCPT_RESERVED 0
struct host1x_syncpt {
int id;
atomic_t min_val;
......@@ -146,6 +149,9 @@ static inline int host1x_syncpt_is_valid(struct host1x_syncpt *sp)
return sp->id < host1x_syncpt_nb_pts(sp->host);
}
/* Patch a wait by replacing it with a wait for syncpt 0 value 0 */
int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr);
/* Return id of the sync point */
u32 host1x_syncpt_id(struct host1x_syncpt *sp);
......
......@@ -37,6 +37,171 @@ DECLARE_EVENT_CLASS(host1x,
TP_printk("name=%s", __entry->name)
);
DEFINE_EVENT(host1x, host1x_channel_open,
TP_PROTO(const char *name),
TP_ARGS(name)
);
DEFINE_EVENT(host1x, host1x_channel_release,
TP_PROTO(const char *name),
TP_ARGS(name)
);
DEFINE_EVENT(host1x, host1x_cdma_begin,
TP_PROTO(const char *name),
TP_ARGS(name)
);
DEFINE_EVENT(host1x, host1x_cdma_end,
TP_PROTO(const char *name),
TP_ARGS(name)
);
TRACE_EVENT(host1x_cdma_push,
TP_PROTO(const char *name, u32 op1, u32 op2),
TP_ARGS(name, op1, op2),
TP_STRUCT__entry(
__field(const char *, name)
__field(u32, op1)
__field(u32, op2)
),
TP_fast_assign(
__entry->name = name;
__entry->op1 = op1;
__entry->op2 = op2;
),
TP_printk("name=%s, op1=%08x, op2=%08x",
__entry->name, __entry->op1, __entry->op2)
);
TRACE_EVENT(host1x_cdma_push_gather,
TP_PROTO(const char *name, u32 mem_id,
u32 words, u32 offset, void *cmdbuf),
TP_ARGS(name, mem_id, words, offset, cmdbuf),
TP_STRUCT__entry(
__field(const char *, name)
__field(u32, mem_id)
__field(u32, words)
__field(u32, offset)
__field(bool, cmdbuf)
__dynamic_array(u32, cmdbuf, words)
),
TP_fast_assign(
if (cmdbuf) {
memcpy(__get_dynamic_array(cmdbuf), cmdbuf+offset,
words * sizeof(u32));
}
__entry->cmdbuf = cmdbuf;
__entry->name = name;
__entry->mem_id = mem_id;
__entry->words = words;
__entry->offset = offset;
),
TP_printk("name=%s, mem_id=%08x, words=%u, offset=%d, contents=[%s]",
__entry->name, __entry->mem_id,
__entry->words, __entry->offset,
__print_hex(__get_dynamic_array(cmdbuf),
__entry->cmdbuf ? __entry->words * 4 : 0))
);
TRACE_EVENT(host1x_channel_submit,
TP_PROTO(const char *name, u32 cmdbufs, u32 relocs, u32 waitchks,
u32 syncpt_id, u32 syncpt_incrs),
TP_ARGS(name, cmdbufs, relocs, waitchks, syncpt_id, syncpt_incrs),
TP_STRUCT__entry(
__field(const char *, name)
__field(u32, cmdbufs)
__field(u32, relocs)
__field(u32, waitchks)
__field(u32, syncpt_id)
__field(u32, syncpt_incrs)
),
TP_fast_assign(
__entry->name = name;
__entry->cmdbufs = cmdbufs;
__entry->relocs = relocs;
__entry->waitchks = waitchks;
__entry->syncpt_id = syncpt_id;
__entry->syncpt_incrs = syncpt_incrs;
),
TP_printk("name=%s, cmdbufs=%u, relocs=%u, waitchks=%d,"
"syncpt_id=%u, syncpt_incrs=%u",
__entry->name, __entry->cmdbufs, __entry->relocs, __entry->waitchks,
__entry->syncpt_id, __entry->syncpt_incrs)
);
TRACE_EVENT(host1x_channel_submitted,
TP_PROTO(const char *name, u32 syncpt_base, u32 syncpt_max),
TP_ARGS(name, syncpt_base, syncpt_max),
TP_STRUCT__entry(
__field(const char *, name)
__field(u32, syncpt_base)
__field(u32, syncpt_max)
),
TP_fast_assign(
__entry->name = name;
__entry->syncpt_base = syncpt_base;
__entry->syncpt_max = syncpt_max;
),
TP_printk("name=%s, syncpt_base=%d, syncpt_max=%d",
__entry->name, __entry->syncpt_base, __entry->syncpt_max)
);
TRACE_EVENT(host1x_channel_submit_complete,
TP_PROTO(const char *name, int count, u32 thresh),
TP_ARGS(name, count, thresh),
TP_STRUCT__entry(
__field(const char *, name)
__field(int, count)
__field(u32, thresh)
),
TP_fast_assign(
__entry->name = name;
__entry->count = count;
__entry->thresh = thresh;
),
TP_printk("name=%s, count=%d, thresh=%d",
__entry->name, __entry->count, __entry->thresh)
);
TRACE_EVENT(host1x_wait_cdma,
TP_PROTO(const char *name, u32 eventid),
TP_ARGS(name, eventid),
TP_STRUCT__entry(
__field(const char *, name)
__field(u32, eventid)
),
TP_fast_assign(
__entry->name = name;
__entry->eventid = eventid;
),
TP_printk("name=%s, event=%d", __entry->name, __entry->eventid)
);
TRACE_EVENT(host1x_syncpt_load_min,
TP_PROTO(u32 id, u32 val),
......@@ -55,6 +220,33 @@ TRACE_EVENT(host1x_syncpt_load_min,
TP_printk("id=%d, val=%d", __entry->id, __entry->val)
);
TRACE_EVENT(host1x_syncpt_wait_check,
TP_PROTO(void *mem_id, u32 offset, u32 syncpt_id, u32 thresh, u32 min),
TP_ARGS(mem_id, offset, syncpt_id, thresh, min),
TP_STRUCT__entry(
__field(void *, mem_id)
__field(u32, offset)
__field(u32, syncpt_id)
__field(u32, thresh)
__field(u32, min)
),
TP_fast_assign(
__entry->mem_id = mem_id;
__entry->offset = offset;
__entry->syncpt_id = syncpt_id;
__entry->thresh = thresh;
__entry->min = min;
),
TP_printk("mem_id=%p, offset=%05x, id=%d, thresh=%d, current=%d",
__entry->mem_id, __entry->offset,
__entry->syncpt_id, __entry->thresh,
__entry->min)
);
#endif /* _TRACE_HOST1X_H */
/* This part must be outside protection */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment