Files
android_kernel_samsung_sm87…/qcom/opensource/graphics-kernel/adreno_dispatch.c

2828 lines
79 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2025, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <soc/qcom/msm_performance.h>
#include "adreno.h"
#include "adreno_sysfs.h"
#include "adreno_trace.h"
#include "kgsl_bus.h"
#include "kgsl_eventlog.h"
#include "kgsl_gmu_core.h"
#include "kgsl_timeline.h"
#define DRAWQUEUE_NEXT(_i, _s) (((_i) + 1) % (_s))
/* Number of commands that can be queued in a context before it sleeps */
static unsigned int _context_drawqueue_size = 50;
/* Number of milliseconds to wait for the context queue to clear */
static unsigned int _context_queue_wait = 10000;
/* Number of drawobjs sent at a time from a single context */
static unsigned int _context_drawobj_burst = 5;
/*
* GFT throttle parameters. If GFT recovered more than
* X times in Y ms invalidate the context and do not attempt recovery.
* X -> _fault_throttle_burst
* Y -> _fault_throttle_time
*/
static unsigned int _fault_throttle_time = 3000;
static unsigned int _fault_throttle_burst = 3;
/*
* Maximum ringbuffer inflight for the single submitting context case - this
* should be sufficiently high to keep the GPU loaded
*/
static unsigned int _dispatcher_q_inflight_hi = 15;
/*
* Minimum inflight for the multiple context case - this should sufficiently low
* to allow for lower latency context switching
*/
static unsigned int _dispatcher_q_inflight_lo = 4;
/* Command batch timeout (in milliseconds) */
unsigned int adreno_drawobj_timeout = 2000;
/* Interval for reading and comparing fault detection registers */
static unsigned int _fault_timer_interval = 200;
/* Use a kmem cache to speed up allocations for dispatcher jobs */
static struct kmem_cache *jobs_cache;
#define DRAWQUEUE_RB(_drawqueue) \
((struct adreno_ringbuffer *) \
container_of((_drawqueue),\
struct adreno_ringbuffer, dispatch_q))
#define DRAWQUEUE(_ringbuffer) (&(_ringbuffer)->dispatch_q)
static bool adreno_drawqueue_is_empty(struct adreno_dispatcher_drawqueue *drawqueue)
{
return (drawqueue && drawqueue->head == drawqueue->tail);
}
static int adreno_dispatch_retire_drawqueue(struct adreno_device *adreno_dev,
struct adreno_dispatcher_drawqueue *drawqueue);
static inline bool drawqueue_is_current(
struct adreno_dispatcher_drawqueue *drawqueue)
{
struct adreno_ringbuffer *rb = DRAWQUEUE_RB(drawqueue);
struct adreno_device *adreno_dev = ADRENO_RB_DEVICE(rb);
return (adreno_dev->cur_rb == rb);
}
/*
* If only one context has queued in the last 100 milliseconds increase
* inflight to a high number to load up the GPU. If multiple contexts
* have queued drop the inflight for better context switch latency.
* If no contexts have queued what are you even doing here?
*/
static inline int
_drawqueue_inflight(struct adreno_dispatcher_drawqueue *drawqueue)
{
return (drawqueue->active_context_count > 1)
? _dispatcher_q_inflight_lo : _dispatcher_q_inflight_hi;
}
/**
* _retire_timestamp() - Retire object without sending it
* to the hardware
* @drawobj: Pointer to the object to retire
*
* In some cases ibs can be retired by the software
* without going to the GPU. In those cases, update the
* memstore from the CPU, kick off the event engine to handle
* expired events and destroy the ib.
*/
static void _retire_timestamp(struct kgsl_drawobj *drawobj)
{
struct kgsl_context *context = drawobj->context;
struct adreno_context *drawctxt = ADRENO_CONTEXT(context);
struct kgsl_device *device = context->device;
struct adreno_ringbuffer *rb = drawctxt->rb;
struct retire_info info = {0};
/*
* Write the start and end timestamp to the memstore to keep the
* accounting sane
*/
kgsl_sharedmem_writel(device->memstore,
KGSL_MEMSTORE_OFFSET(context->id, soptimestamp),
drawobj->timestamp);
kgsl_sharedmem_writel(device->memstore,
KGSL_MEMSTORE_OFFSET(context->id, eoptimestamp),
drawobj->timestamp);
drawctxt->submitted_timestamp = drawobj->timestamp;
/* Retire pending GPU events for the object */
kgsl_process_event_group(device, &context->events);
info.inflight = -1;
info.rb_id = rb->id;
info.wptr = rb->wptr;
info.timestamp = drawobj->timestamp;
msm_perf_events_update(MSM_PERF_GFX, MSM_PERF_RETIRED,
pid_nr(context->proc_priv->pid),
context->id, drawobj->timestamp,
!!(drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME));
if (drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME) {
atomic64_inc(&context->proc_priv->frame_count);
atomic_inc(&context->proc_priv->period->frames);
}
info.rptr = adreno_get_rptr(rb);
trace_adreno_cmdbatch_retired(context, &info,
drawobj->flags, rb->dispatch_q.inflight, 0);
log_kgsl_cmdbatch_retired_event(context->id, drawobj->timestamp,
context->priority, drawobj->flags, 0, 0);
kgsl_drawobj_destroy(drawobj);
}
static int _check_context_queue(struct adreno_context *drawctxt, u32 count)
{
int ret;
spin_lock(&drawctxt->lock);
/*
* Wake up if there is room in the context or if the whole thing got
* invalidated while we were asleep
*/
if (kgsl_context_invalid(&drawctxt->base))
ret = 1;
else
ret = ((drawctxt->queued + count) < _context_drawqueue_size) ? 1 : 0;
spin_unlock(&drawctxt->lock);
return ret;
}
/*
* return true if this is a marker command and the dependent timestamp has
* retired
*/
static bool _marker_expired(struct kgsl_drawobj_cmd *markerobj)
{
struct kgsl_drawobj *drawobj = DRAWOBJ(markerobj);
return (drawobj->flags & KGSL_DRAWOBJ_MARKER) &&
kgsl_check_timestamp(drawobj->device, drawobj->context,
markerobj->marker_timestamp);
}
static inline void _pop_drawobj(struct adreno_context *drawctxt)
{
drawctxt->drawqueue_head = DRAWQUEUE_NEXT(drawctxt->drawqueue_head,
ADRENO_CONTEXT_DRAWQUEUE_SIZE);
drawctxt->queued--;
}
static int dispatch_retire_markerobj(struct kgsl_drawobj *drawobj,
struct adreno_context *drawctxt)
{
struct kgsl_drawobj_cmd *cmdobj = CMDOBJ(drawobj);
if (_marker_expired(cmdobj)) {
_pop_drawobj(drawctxt);
_retire_timestamp(drawobj);
return 0;
}
/*
* If the marker isn't expired but the SKIP bit
* is set then there are real commands following
* this one in the queue. This means that we
* need to dispatch the command so that we can
* keep the timestamp accounting correct. If
* skip isn't set then we block this queue
* until the dependent timestamp expires
*/
return test_bit(CMDOBJ_SKIP, &cmdobj->priv) ? 1 : -EAGAIN;
}
static int dispatch_retire_syncobj(struct kgsl_drawobj *drawobj,
struct adreno_context *drawctxt)
{
struct kgsl_drawobj_sync *syncobj = SYNCOBJ(drawobj);
if (!kgsl_drawobj_events_pending(syncobj)) {
_pop_drawobj(drawctxt);
kgsl_drawobj_destroy(drawobj);
return 0;
}
/*
* If we got here, there are pending events for sync object.
* Start the canary timer if it hasnt been started already.
*/
if (!syncobj->timeout_jiffies) {
syncobj->timeout_jiffies = jiffies + msecs_to_jiffies(5000);
mod_timer(&syncobj->timer, syncobj->timeout_jiffies);
}
return -EAGAIN;
}
static int drawqueue_retire_timelineobj(struct kgsl_drawobj *drawobj,
struct adreno_context *drawctxt)
{
_pop_drawobj(drawctxt);
kgsl_drawobj_destroy(drawobj);
return 0;
}
static int drawqueue_retire_bindobj(struct kgsl_drawobj *drawobj,
struct adreno_context *drawctxt)
{
struct kgsl_drawobj_bind *bindobj = BINDOBJ(drawobj);
if (test_bit(KGSL_BINDOBJ_STATE_DONE, &bindobj->state)) {
_pop_drawobj(drawctxt);
_retire_timestamp(drawobj);
return 0;
}
if (!test_and_set_bit(KGSL_BINDOBJ_STATE_START, &bindobj->state)) {
/*
* Take a referencre to the drawobj and the context because both
* get referenced in the bind callback
*/
_kgsl_context_get(&drawctxt->base);
kref_get(&drawobj->refcount);
kgsl_sharedmem_bind_ranges(bindobj->bind);
}
return -EAGAIN;
}
/*
* Retires all expired marker and sync objs from the context
* queue and returns one of the below
* a) next drawobj that needs to be sent to ringbuffer
* b) -EAGAIN for syncobj with syncpoints pending.
* c) -EAGAIN for markerobj whose marker timestamp has not expired yet.
* c) NULL for no commands remaining in drawqueue.
*/
static struct kgsl_drawobj *_process_drawqueue_get_next_drawobj(
struct adreno_context *drawctxt)
{
struct kgsl_drawobj *drawobj;
unsigned int i = drawctxt->drawqueue_head;
if (drawctxt->drawqueue_head == drawctxt->drawqueue_tail)
return NULL;
for (i = drawctxt->drawqueue_head; i != drawctxt->drawqueue_tail;
i = DRAWQUEUE_NEXT(i, ADRENO_CONTEXT_DRAWQUEUE_SIZE)) {
int ret = 0;
drawobj = drawctxt->drawqueue[i];
if (!drawobj)
return NULL;
switch (drawobj->type) {
case CMDOBJ_TYPE:
return drawobj;
case MARKEROBJ_TYPE:
ret = dispatch_retire_markerobj(drawobj, drawctxt);
/* Special case where marker needs to be sent to GPU */
if (ret == 1)
return drawobj;
break;
case SYNCOBJ_TYPE:
ret = dispatch_retire_syncobj(drawobj, drawctxt);
break;
case BINDOBJ_TYPE:
ret = drawqueue_retire_bindobj(drawobj, drawctxt);
break;
case TIMELINEOBJ_TYPE:
ret = drawqueue_retire_timelineobj(drawobj, drawctxt);
break;
default:
ret = -EINVAL;
break;
}
if (ret)
return ERR_PTR(ret);
}
return NULL;
}
/**
* adreno_dispatcher_requeue_cmdobj() - Put a command back on the context
* queue
* @drawctxt: Pointer to the adreno draw context
* @cmdobj: Pointer to the KGSL command object to requeue
*
* Failure to submit a command to the ringbuffer isn't the fault of the command
* being submitted so if a failure happens, push it back on the head of the
* context queue to be reconsidered again unless the context got detached.
*/
static inline int adreno_dispatcher_requeue_cmdobj(
struct adreno_context *drawctxt,
struct kgsl_drawobj_cmd *cmdobj)
{
unsigned int prev;
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
spin_lock(&drawctxt->lock);
if (kgsl_context_is_bad(&drawctxt->base)) {
spin_unlock(&drawctxt->lock);
/* get rid of this drawobj since the context is bad */
kgsl_drawobj_destroy(drawobj);
return -ENOENT;
}
prev = drawctxt->drawqueue_head == 0 ?
(ADRENO_CONTEXT_DRAWQUEUE_SIZE - 1) :
(drawctxt->drawqueue_head - 1);
/*
* The maximum queue size always needs to be one less then the size of
* the ringbuffer queue so there is "room" to put the drawobj back in
*/
WARN_ON(prev == drawctxt->drawqueue_tail);
drawctxt->drawqueue[prev] = drawobj;
drawctxt->queued++;
/* Reset the command queue head to reflect the newly requeued change */
drawctxt->drawqueue_head = prev;
cmdobj->requeue_cnt++;
spin_unlock(&drawctxt->lock);
return 0;
}
/**
* dispatcher_queue_context() - Queue a context in the dispatcher pending list
* @dispatcher: Pointer to the adreno dispatcher struct
* @drawctxt: Pointer to the adreno draw context
*
* Add a context to the dispatcher pending list.
*/
static int dispatcher_queue_context(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
struct adreno_dispatch_job *job;
/* Refuse to queue a detached context */
if (kgsl_context_detached(&drawctxt->base))
return 0;
if (!_kgsl_context_get(&drawctxt->base))
return 0;
/* This function can be called in an atomic context */
job = kmem_cache_alloc(jobs_cache, GFP_ATOMIC);
if (!job) {
kgsl_context_put(&drawctxt->base);
return -ENOMEM;
}
job->drawctxt = drawctxt;
trace_dispatch_queue_context(drawctxt);
llist_add(&job->node, &dispatcher->jobs[drawctxt->base.priority]);
return 0;
}
/*
* Real time clients may demand high BW and have strict latency requirement.
* GPU bus DCVS is not fast enough to account for sudden BW requirements.
* Bus hint helps to bump up the bus vote (IB) upfront for known time-critical
* workloads.
*/
static void process_rt_bus_hint(struct kgsl_device *device, bool on)
{
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
struct adreno_dispatcher_drawqueue *drawqueue =
DRAWQUEUE(&adreno_dev->ringbuffers[0]);
if (!adreno_is_preemption_enabled(adreno_dev) ||
!device->pwrctrl.rt_bus_hint)
return;
if (device->pwrctrl.rt_bus_hint_active == on)
return;
if (on && drawqueue->inflight == 1)
kgsl_bus_update(device, KGSL_BUS_VOTE_RT_HINT_ON);
if (!on && drawqueue->inflight == 0)
kgsl_bus_update(device, KGSL_BUS_VOTE_RT_HINT_OFF);
}
#define ADRENO_DRAWOBJ_PROFILE_COUNT \
(PAGE_SIZE / sizeof(struct adreno_drawobj_profile_entry))
/**
* sendcmd() - Send a drawobj to the GPU hardware
* @dispatcher: Pointer to the adreno dispatcher struct
* @drawobj: Pointer to the KGSL drawobj being sent
*
* Send a KGSL drawobj to the GPU hardware
*/
static int sendcmd(struct adreno_device *adreno_dev,
struct kgsl_drawobj_cmd *cmdobj)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
struct adreno_context *drawctxt = ADRENO_CONTEXT(drawobj->context);
struct kgsl_context *context = drawobj->context;
struct adreno_dispatcher_drawqueue *dispatch_q = &drawctxt->rb->dispatch_q;
struct adreno_submit_time time;
uint64_t secs = 0;
unsigned long nsecs = 0;
int ret;
struct submission_info info = {0};
int is_current_rt = rt_task(current);
int nice = task_nice(current);
mutex_lock(&device->mutex);
/* Elevating threads priority to avoid context switch with holding device mutex */
if (!is_current_rt)
sched_set_fifo(current);
if (adreno_gpu_halt(adreno_dev) != 0) {
ret = -EBUSY;
goto err;
}
memset(&time, 0x0, sizeof(time));
dispatcher->inflight++;
dispatch_q->inflight++;
if (dispatcher->inflight == 1 &&
!test_bit(ADRENO_DISPATCHER_POWER, &dispatcher->priv)) {
/* Time to make the donuts. Turn on the GPU */
ret = adreno_active_count_get(adreno_dev);
if (ret) {
dispatcher->inflight--;
dispatch_q->inflight--;
goto err;
}
set_bit(ADRENO_DISPATCHER_POWER, &dispatcher->priv);
}
if (test_bit(ADRENO_DEVICE_DRAWOBJ_PROFILE, &adreno_dev->priv)) {
set_bit(CMDOBJ_PROFILE, &cmdobj->priv);
cmdobj->profile_index = adreno_dev->profile_index;
adreno_dev->profile_index =
(adreno_dev->profile_index + 1) %
ADRENO_DRAWOBJ_PROFILE_COUNT;
}
process_rt_bus_hint(device, true);
ret = adreno_ringbuffer_submitcmd(adreno_dev, cmdobj, &time);
/*
* On the first command, if the submission was successful, then read the
* fault registers. If it failed then turn off the GPU. Sad face.
*/
if (dispatcher->inflight == 1) {
if (ret == 0) {
if (!test_and_set_bit(ADRENO_DISPATCHER_ACTIVE,
&dispatcher->priv))
reinit_completion(&dispatcher->idle_gate);
} else {
adreno_active_count_put(adreno_dev);
clear_bit(ADRENO_DISPATCHER_POWER, &dispatcher->priv);
}
}
if (ret) {
dispatcher->inflight--;
dispatch_q->inflight--;
process_rt_bus_hint(device, false);
/*
* Don't log a message in case of:
* -ENOENT means that the context was detached before the
* command was submitted
* -ENOSPC means that there temporarily isn't any room in the
* ringbuffer
* -PROTO means that a fault is currently being worked
*/
if (ret != -ENOENT && ret != -ENOSPC && ret != -EPROTO)
dev_err(device->dev,
"Unable to submit command to the ringbuffer %d\n",
ret);
goto err;
}
secs = time.ktime;
nsecs = do_div(secs, 1000000000);
/*
* For the first submission in any given command queue update the
* expected expire time - this won't actually be used / updated until
* the command queue in question goes current, but universally setting
* it here avoids the possibilty of some race conditions with preempt
*/
if (dispatch_q->inflight == 1)
dispatch_q->expires = jiffies +
msecs_to_jiffies(adreno_drawobj_timeout);
info.inflight = (int) dispatcher->inflight;
info.rb_id = drawctxt->rb->id;
info.rptr = adreno_get_rptr(drawctxt->rb);
info.wptr = drawctxt->rb->wptr;
info.gmu_dispatch_queue = -1;
msm_perf_events_update(MSM_PERF_GFX, MSM_PERF_SUBMIT,
pid_nr(context->proc_priv->pid),
context->id, drawobj->timestamp,
!!(drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME));
trace_adreno_cmdbatch_submitted(drawobj, &info,
time.ticks, (unsigned long) secs, nsecs / 1000,
dispatch_q->inflight);
log_kgsl_cmdbatch_submitted_event(context->id, drawobj->timestamp,
context->priority, drawobj->flags);
if (!is_current_rt)
sched_set_normal(current, nice);
mutex_unlock(&device->mutex);
cmdobj->submit_ticks = time.ticks;
dispatch_q->cmd_q[dispatch_q->tail] = cmdobj;
dispatch_q->tail = (dispatch_q->tail + 1) %
ADRENO_DISPATCH_DRAWQUEUE_SIZE;
/*
* If we believe ourselves to be current and preemption isn't a thing,
* then set up the timer. If this misses, then preemption is indeed a
* thing and the timer will be set up in due time
*/
if (adreno_in_preempt_state(adreno_dev, ADRENO_PREEMPT_NONE)) {
if (drawqueue_is_current(dispatch_q))
mod_timer(&dispatcher->timer, dispatch_q->expires);
}
/*
* we just submitted something, readjust ringbuffer
* execution level
*/
if (gpudev->preemption_schedule)
gpudev->preemption_schedule(adreno_dev);
return 0;
err:
if (!is_current_rt)
sched_set_normal(current, nice);
mutex_unlock(&device->mutex);
return ret;
}
/**
* dispatcher_context_sendcmds() - Send commands from a context to the GPU
* @adreno_dev: Pointer to the adreno device struct
* @drawctxt: Pointer to the adreno context to dispatch commands from
*
* Dequeue and send a burst of commands from the specified context to the GPU
* Returns postive if the context needs to be put back on the pending queue
* 0 if the context is empty or detached and negative on error
*/
static int dispatcher_context_sendcmds(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt)
{
struct adreno_dispatcher_drawqueue *dispatch_q =
&(drawctxt->rb->dispatch_q);
int count = 0;
int ret = 0;
int inflight = _drawqueue_inflight(dispatch_q);
unsigned int timestamp;
if (dispatch_q->inflight >= inflight) {
spin_lock(&drawctxt->lock);
_process_drawqueue_get_next_drawobj(drawctxt);
spin_unlock(&drawctxt->lock);
return -EBUSY;
}
/*
* Each context can send a specific number of drawobjs per cycle
*/
while ((count < _context_drawobj_burst) &&
(dispatch_q->inflight < inflight)) {
struct kgsl_drawobj *drawobj;
struct kgsl_drawobj_cmd *cmdobj;
struct kgsl_context *context;
if (adreno_gpu_fault(adreno_dev) != 0)
break;
spin_lock(&drawctxt->lock);
drawobj = _process_drawqueue_get_next_drawobj(drawctxt);
/*
* adreno_context_get_drawobj returns -EAGAIN if the current
* drawobj has pending sync points so no more to do here.
* When the sync points are satisfied then the context will get
* reqeueued
*/
if (IS_ERR_OR_NULL(drawobj)) {
if (IS_ERR(drawobj))
ret = PTR_ERR(drawobj);
spin_unlock(&drawctxt->lock);
break;
}
_pop_drawobj(drawctxt);
spin_unlock(&drawctxt->lock);
timestamp = drawobj->timestamp;
cmdobj = CMDOBJ(drawobj);
context = drawobj->context;
trace_adreno_cmdbatch_ready(context->id, context->priority,
drawobj->timestamp, cmdobj->requeue_cnt);
ret = sendcmd(adreno_dev, cmdobj);
/*
* On error from sendcmd() try to requeue the cmdobj
* unless we got back -ENOENT which means that the context has
* been detached and there will be no more deliveries from here
*/
if (ret != 0) {
/* Destroy the cmdobj on -ENOENT */
if (ret == -ENOENT)
kgsl_drawobj_destroy(drawobj);
else {
/*
* If the requeue returns an error, return that
* instead of whatever sendcmd() sent us
*/
int r = adreno_dispatcher_requeue_cmdobj(
drawctxt, cmdobj);
if (r)
ret = r;
}
break;
}
drawctxt->submitted_timestamp = timestamp;
count++;
}
/*
* Wake up any snoozing threads if we have consumed any real commands
* or marker commands and we have room in the context queue.
*/
if (_check_context_queue(drawctxt, 0))
wake_up_all(&drawctxt->wq);
if (!ret)
ret = count;
/* Return error or the number of commands queued */
return ret;
}
static bool adreno_gpu_stopped(struct adreno_device *adreno_dev)
{
return (adreno_gpu_fault(adreno_dev) || adreno_gpu_halt(adreno_dev));
}
static void dispatcher_handle_jobs_list(struct adreno_device *adreno_dev,
int id, unsigned long *map, struct llist_node *list)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
struct adreno_dispatch_job *job, *next;
if (!list)
return;
/* Reverse the order so the oldest context is considered first */
list = llist_reverse_order(list);
llist_for_each_entry_safe(job, next, list, node) {
int ret;
if (kgsl_context_is_bad(&job->drawctxt->base)) {
kgsl_context_put(&job->drawctxt->base);
kmem_cache_free(jobs_cache, job);
continue;
}
/*
* Due to the nature of the lockless queue the same context
* might have multiple jobs on the list. We allow this so we
* don't have to query the list on the producer side but on the
* consumer side we only want each context to be considered
* once. Use a bitmap to remember which contexts we've already
* seen and quietly discard duplicate jobs
*/
if (test_and_set_bit(job->drawctxt->base.id, map)) {
kgsl_context_put(&job->drawctxt->base);
kmem_cache_free(jobs_cache, job);
continue;
}
/*
* If gpu is in fault or dispatcher is halted, add back the jobs
* so that they are processed after recovery or when dispatcher
* is resumed.
*/
if (adreno_gpu_stopped(adreno_dev)) {
llist_add(&job->node, &dispatcher->jobs[id]);
continue;
}
ret = dispatcher_context_sendcmds(adreno_dev, job->drawctxt);
/*
* If the context had nothing queued or the context has been
* destroyed then drop the job
*/
if (!ret || ret == -ENOENT) {
kgsl_context_put(&job->drawctxt->base);
kmem_cache_free(jobs_cache, job);
continue;
}
/*
* If the ringbuffer is full then requeue the job to be
* considered first next time. Otherwise the context
* either successfully submmitted to the GPU or another error
* happened and it should go back on the regular queue
*/
if (ret == -EBUSY)
llist_add(&job->node, &dispatcher->requeue[id]);
else
llist_add(&job->node, &dispatcher->jobs[id]);
}
}
static void dispatcher_handle_jobs(struct adreno_device *adreno_dev, int id)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
unsigned long map[BITS_TO_LONGS(KGSL_MEMSTORE_MAX)];
struct llist_node *requeue, *jobs;
memset(map, 0, sizeof(map));
requeue = llist_del_all(&dispatcher->requeue[id]);
jobs = llist_del_all(&dispatcher->jobs[id]);
dispatcher_handle_jobs_list(adreno_dev, id, map, requeue);
dispatcher_handle_jobs_list(adreno_dev, id, map, jobs);
}
/**
* _adreno_dispatcher_issuecmds() - Issue commmands from pending contexts
* @adreno_dev: Pointer to the adreno device struct
*
* Issue as many commands as possible (up to inflight) from the pending contexts
* This function assumes the dispatcher mutex has been locked.
*/
static void _adreno_dispatcher_issuecmds(struct adreno_device *adreno_dev)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
int i;
/* Leave early if the dispatcher isn't in a happy state */
if (adreno_gpu_fault(adreno_dev) != 0)
return;
for (i = 0; i < ARRAY_SIZE(dispatcher->jobs); i++)
dispatcher_handle_jobs(adreno_dev, i);
}
/* Update the dispatcher timers */
static void _dispatcher_update_timers(struct adreno_device *adreno_dev)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
/* Kick the idle timer */
mutex_lock(&device->mutex);
kgsl_pwrscale_update(device);
process_rt_bus_hint(device, false);
kgsl_start_idle_timer(device);
mutex_unlock(&device->mutex);
/* Check to see if we need to update the command timer */
if (adreno_in_preempt_state(adreno_dev, ADRENO_PREEMPT_NONE)) {
struct adreno_dispatcher_drawqueue *drawqueue =
DRAWQUEUE(adreno_dev->cur_rb);
if (!adreno_drawqueue_is_empty(drawqueue))
mod_timer(&dispatcher->timer, drawqueue->expires);
}
}
static inline void _decrement_submit_now(struct kgsl_device *device)
{
spin_lock(&device->submit_lock);
device->submit_now--;
spin_unlock(&device->submit_lock);
}
/**
* adreno_dispatcher_issuecmds() - Issue commmands from pending contexts
* @adreno_dev: Pointer to the adreno device struct
*
* Lock the dispatcher and call _adreno_dispatcher_issueibcmds
*/
static void adreno_dispatcher_issuecmds(struct adreno_device *adreno_dev)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
spin_lock(&device->submit_lock);
/* If state is not ACTIVE, schedule the work for later */
if (device->skip_inline_submit) {
spin_unlock(&device->submit_lock);
goto done;
}
device->submit_now++;
spin_unlock(&device->submit_lock);
/* If the dispatcher is busy then schedule the work for later */
if (!mutex_trylock(&dispatcher->mutex)) {
_decrement_submit_now(device);
goto done;
}
_adreno_dispatcher_issuecmds(adreno_dev);
if (dispatcher->inflight)
_dispatcher_update_timers(adreno_dev);
mutex_unlock(&dispatcher->mutex);
_decrement_submit_now(device);
return;
done:
adreno_scheduler_queue(adreno_dev);
}
/**
* get_timestamp() - Return the next timestamp for the context
* @drawctxt - Pointer to an adreno draw context struct
* @drawobj - Pointer to a drawobj
* @timestamp - Pointer to a timestamp value possibly passed from the user
* @user_ts - user generated timestamp
*
* Assign a timestamp based on the settings of the draw context and the command
* batch.
*/
static int get_timestamp(struct adreno_context *drawctxt,
struct kgsl_drawobj *drawobj, unsigned int *timestamp,
unsigned int user_ts)
{
if (drawctxt->base.flags & KGSL_CONTEXT_USER_GENERATED_TS) {
/*
* User specified timestamps need to be greater than the last
* issued timestamp in the context
*/
if (timestamp_cmp(drawctxt->timestamp, user_ts) >= 0)
return -ERANGE;
drawctxt->timestamp = user_ts;
} else
drawctxt->timestamp++;
*timestamp = drawctxt->timestamp;
drawobj->timestamp = *timestamp;
return 0;
}
static void _set_ft_policy(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt,
struct kgsl_drawobj_cmd *cmdobj)
{
/*
* Set the fault tolerance policy for the command batch - assuming the
* context hasn't disabled FT use the current device policy
*/
if (drawctxt->base.flags & KGSL_CONTEXT_NO_FAULT_TOLERANCE)
set_bit(KGSL_FT_DISABLE, &cmdobj->fault_policy);
/*
* Set the fault tolerance policy to FT_REPLAY - As context wants
* to invalidate it after a replay attempt fails. This doesn't
* require to execute the default FT policy.
*/
else if (drawctxt->base.flags & KGSL_CONTEXT_INVALIDATE_ON_FAULT)
set_bit(KGSL_FT_REPLAY, &cmdobj->fault_policy);
else
cmdobj->fault_policy = adreno_dev->ft_policy;
}
static void _cmdobj_set_flags(struct adreno_context *drawctxt,
struct kgsl_drawobj_cmd *cmdobj)
{
/*
* Force the preamble for this submission only - this is usually
* requested by the dispatcher as part of fault recovery
*/
if (test_and_clear_bit(ADRENO_CONTEXT_FORCE_PREAMBLE,
&drawctxt->base.priv))
set_bit(CMDOBJ_FORCE_PREAMBLE, &cmdobj->priv);
/*
* Force the premable if set from userspace in the context or
* command obj flags
*/
if ((drawctxt->base.flags & KGSL_CONTEXT_CTX_SWITCH) ||
(cmdobj->base.flags & KGSL_DRAWOBJ_CTX_SWITCH))
set_bit(CMDOBJ_FORCE_PREAMBLE, &cmdobj->priv);
/* Skip this ib if IFH_NOP is enabled */
if (drawctxt->base.flags & KGSL_CONTEXT_IFH_NOP)
set_bit(CMDOBJ_SKIP, &cmdobj->priv);
/*
* If we are waiting for the end of frame and it hasn't appeared yet,
* then mark the command obj as skipped. It will still progress
* through the pipeline but it won't actually send any commands
*/
if (test_bit(ADRENO_CONTEXT_SKIP_EOF, &drawctxt->base.priv)) {
set_bit(CMDOBJ_SKIP, &cmdobj->priv);
/*
* If this command obj represents the EOF then clear the way
* for the dispatcher to continue submitting
*/
if (cmdobj->base.flags & KGSL_DRAWOBJ_END_OF_FRAME) {
clear_bit(ADRENO_CONTEXT_SKIP_EOF,
&drawctxt->base.priv);
/*
* Force the preamble on the next command to ensure that
* the state is correct
*/
set_bit(ADRENO_CONTEXT_FORCE_PREAMBLE,
&drawctxt->base.priv);
}
}
}
static inline int _wait_for_room_in_context_queue(
struct adreno_context *drawctxt, u32 count) __must_hold(&drawctxt->lock)
{
int ret = 0;
/*
* There is always a possibility that dispatcher may end up pushing
* the last popped draw object back to the context drawqueue. Hence,
* we can only queue up to _context_drawqueue_size - 1 here to make
* sure we never let drawqueue->queued exceed _context_drawqueue_size.
*/
if ((drawctxt->queued + count) > (_context_drawqueue_size - 1)) {
trace_adreno_drawctxt_sleep(drawctxt);
spin_unlock(&drawctxt->lock);
ret = wait_event_interruptible_timeout(drawctxt->wq,
_check_context_queue(drawctxt, count),
msecs_to_jiffies(_context_queue_wait));
spin_lock(&drawctxt->lock);
trace_adreno_drawctxt_wake(drawctxt);
/*
* Account for the possibility that the context got invalidated
* while we were sleeping
*/
if (ret > 0)
ret = kgsl_check_context_state(&drawctxt->base);
else if (ret == 0)
ret = -ETIMEDOUT;
}
return ret;
}
static unsigned int _check_context_state_to_queue_cmds(
struct adreno_context *drawctxt, u32 count)
{
int ret = kgsl_check_context_state(&drawctxt->base);
if (ret)
return ret;
return _wait_for_room_in_context_queue(drawctxt, count);
}
static void _queue_drawobj(struct adreno_context *drawctxt,
struct kgsl_drawobj *drawobj)
{
struct kgsl_context *context = drawobj->context;
/* Put the command into the queue */
drawctxt->drawqueue[drawctxt->drawqueue_tail] = drawobj;
drawctxt->drawqueue_tail = (drawctxt->drawqueue_tail + 1) %
ADRENO_CONTEXT_DRAWQUEUE_SIZE;
drawctxt->queued++;
msm_perf_events_update(MSM_PERF_GFX, MSM_PERF_QUEUE,
pid_nr(context->proc_priv->pid),
context->id, drawobj->timestamp,
!!(drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME));
trace_adreno_cmdbatch_queued(drawobj, drawctxt->queued);
}
static int drawctxt_queue_bindobj(struct adreno_context *drawctxt,
struct kgsl_drawobj *drawobj, u32 *timestamp, u32 user_ts)
{
int ret;
ret = get_timestamp(drawctxt, drawobj, timestamp, user_ts);
if (ret)
return ret;
drawctxt->queued_timestamp = *timestamp;
_queue_drawobj(drawctxt, drawobj);
return 0;
}
static void drawctxt_queue_timelineobj(struct adreno_context *drawctxt,
struct kgsl_drawobj *drawobj)
{
/*
* This drawobj is not submitted to the GPU so use a timestamp of 0.
* Update the timestamp through a subsequent marker to keep userspace
* happy.
*/
drawobj->timestamp = 0;
_queue_drawobj(drawctxt, drawobj);
}
static int drawctxt_queue_markerobj(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt, struct kgsl_drawobj *drawobj,
uint32_t *timestamp, unsigned int user_ts)
{
struct kgsl_drawobj_cmd *markerobj = CMDOBJ(drawobj);
int ret;
ret = get_timestamp(drawctxt, drawobj, timestamp, user_ts);
if (ret)
return ret;
/*
* See if we can fastpath this thing - if nothing is queued
* and nothing is inflight retire without bothering the GPU
*/
if (!drawctxt->queued && kgsl_check_timestamp(drawobj->device,
drawobj->context, drawctxt->queued_timestamp)) {
_retire_timestamp(drawobj);
return 1;
}
/*
* Remember the last queued timestamp - the marker will block
* until that timestamp is expired (unless another command
* comes along and forces the marker to execute)
*/
markerobj->marker_timestamp = drawctxt->queued_timestamp;
drawctxt->queued_timestamp = *timestamp;
_set_ft_policy(adreno_dev, drawctxt, markerobj);
_cmdobj_set_flags(drawctxt, markerobj);
_queue_drawobj(drawctxt, drawobj);
return 0;
}
static int drawctxt_queue_cmdobj(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt, struct kgsl_drawobj *drawobj,
uint32_t *timestamp, unsigned int user_ts)
{
struct kgsl_drawobj_cmd *cmdobj = CMDOBJ(drawobj);
unsigned int j;
int ret;
ret = get_timestamp(drawctxt, drawobj, timestamp, user_ts);
if (ret)
return ret;
/*
* If this is a real command then we need to force any markers
* queued before it to dispatch to keep time linear - set the
* skip bit so the commands get NOPed.
*/
j = drawctxt->drawqueue_head;
while (j != drawctxt->drawqueue_tail) {
if (drawctxt->drawqueue[j]->type == MARKEROBJ_TYPE) {
struct kgsl_drawobj_cmd *markerobj =
CMDOBJ(drawctxt->drawqueue[j]);
set_bit(CMDOBJ_SKIP, &markerobj->priv);
}
j = DRAWQUEUE_NEXT(j, ADRENO_CONTEXT_DRAWQUEUE_SIZE);
}
drawctxt->queued_timestamp = *timestamp;
_set_ft_policy(adreno_dev, drawctxt, cmdobj);
_cmdobj_set_flags(drawctxt, cmdobj);
_queue_drawobj(drawctxt, drawobj);
return 0;
}
static void drawctxt_queue_syncobj(struct adreno_context *drawctxt,
struct kgsl_drawobj *drawobj, uint32_t *timestamp)
{
*timestamp = 0;
drawobj->timestamp = 0;
_queue_drawobj(drawctxt, drawobj);
}
/*
* Queue a command in the context - if there isn't any room in the queue, then
* block until there is
*/
static int adreno_dispatcher_queue_cmds(struct kgsl_device_private *dev_priv,
struct kgsl_context *context, struct kgsl_drawobj *drawobj[],
uint32_t count, uint32_t *timestamp)
{
struct kgsl_device *device = dev_priv->device;
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
struct adreno_context *drawctxt = ADRENO_CONTEXT(context);
struct adreno_dispatcher_drawqueue *dispatch_q;
struct adreno_dispatch_job *job;
int ret;
unsigned int i, user_ts;
/*
* There is always a possibility that dispatcher may end up pushing
* the last popped draw object back to the context drawqueue. Hence,
* we can only queue up to _context_drawqueue_size - 1 here to make
* sure we never let drawqueue->queued exceed _context_drawqueue_size.
*/
if (!count || count > _context_drawqueue_size - 1)
return -EINVAL;
ret = kgsl_check_context_state(&drawctxt->base);
if (ret)
return ret;
ret = adreno_verify_cmdobj(dev_priv, context, drawobj, count);
if (ret)
return ret;
/* wait for the suspend gate */
wait_for_completion(&device->halt_gate);
job = kmem_cache_alloc(jobs_cache, GFP_KERNEL);
if (!job)
return -ENOMEM;
job->drawctxt = drawctxt;
spin_lock(&drawctxt->lock);
ret = _check_context_state_to_queue_cmds(drawctxt, count);
if (ret) {
spin_unlock(&drawctxt->lock);
kmem_cache_free(jobs_cache, job);
return ret;
}
user_ts = *timestamp;
/*
* If there is only one drawobj in the array and it is of
* type SYNCOBJ_TYPE, skip comparing user_ts as it can be 0
*/
if (!(count == 1 && drawobj[0]->type == SYNCOBJ_TYPE) &&
(drawctxt->base.flags & KGSL_CONTEXT_USER_GENERATED_TS)) {
/*
* User specified timestamps need to be greater than the last
* issued timestamp in the context
*/
if (timestamp_cmp(drawctxt->timestamp, user_ts) >= 0) {
spin_unlock(&drawctxt->lock);
kmem_cache_free(jobs_cache, job);
return -ERANGE;
}
}
for (i = 0; i < count; i++) {
switch (drawobj[i]->type) {
case MARKEROBJ_TYPE:
ret = drawctxt_queue_markerobj(adreno_dev, drawctxt,
drawobj[i], timestamp, user_ts);
if (ret) {
spin_unlock(&drawctxt->lock);
kmem_cache_free(jobs_cache, job);
}
if (ret == 1)
goto done;
else if (ret)
return ret;
break;
case CMDOBJ_TYPE:
ret = drawctxt_queue_cmdobj(adreno_dev, drawctxt,
drawobj[i], timestamp, user_ts);
if (ret) {
spin_unlock(&drawctxt->lock);
kmem_cache_free(jobs_cache, job);
return ret;
}
break;
case SYNCOBJ_TYPE:
drawctxt_queue_syncobj(drawctxt, drawobj[i], timestamp);
break;
case BINDOBJ_TYPE:
ret = drawctxt_queue_bindobj(drawctxt, drawobj[i],
timestamp, user_ts);
if (ret) {
spin_unlock(&drawctxt->lock);
kmem_cache_free(jobs_cache, job);
return ret;
}
break;
case TIMELINEOBJ_TYPE:
drawctxt_queue_timelineobj(drawctxt, drawobj[i]);
break;
default:
spin_unlock(&drawctxt->lock);
kmem_cache_free(jobs_cache, job);
return -EINVAL;
}
}
dispatch_q = &(ADRENO_CONTEXT(drawobj[0]->context)->rb->dispatch_q);
adreno_track_context(adreno_dev, dispatch_q, drawctxt);
spin_unlock(&drawctxt->lock);
/* Add the context to the dispatcher pending list */
if (_kgsl_context_get(&drawctxt->base)) {
trace_dispatch_queue_context(drawctxt);
llist_add(&job->node,
&adreno_dev->dispatcher.jobs[drawctxt->base.priority]);
} else {
kmem_cache_free(jobs_cache, job);
goto done;
}
/*
* Only issue commands if inflight is less than burst -this prevents us
* from sitting around waiting for the mutex on a busy system - the work
* loop will schedule it for us. Inflight is mutex protected but the
* worse that can happen is that it will go to 0 after we check and if
* it goes to 0 it is because the work loop decremented it and the work
* queue will try to schedule new commands anyway.
*/
if (dispatch_q->inflight < _context_drawobj_burst)
adreno_dispatcher_issuecmds(adreno_dev);
done:
if (test_and_clear_bit(ADRENO_CONTEXT_FAULT, &context->priv))
return -EPROTO;
return 0;
}
/*
* If an IB inside of the drawobj has a gpuaddr that matches the base
* passed in then zero the size which effectively skips it when it is submitted
* in the ringbuffer.
*/
static void _skip_ib(struct kgsl_drawobj_cmd *cmdobj, uint64_t base)
{
struct kgsl_memobj_node *ib;
list_for_each_entry(ib, &cmdobj->cmdlist, node) {
if (ib->gpuaddr == base) {
ib->priv |= MEMOBJ_SKIP;
if (base)
return;
}
}
}
static void _skip_cmd(struct kgsl_drawobj_cmd *cmdobj,
struct kgsl_drawobj_cmd **replay, int count)
{
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
struct adreno_context *drawctxt = ADRENO_CONTEXT(drawobj->context);
int i;
/*
* SKIPCMD policy: next IB issued for this context is tentative
* if it fails we assume that GFT failed and if it succeeds
* we mark GFT as a success.
*
* Find next commandbatch for the faulting context
* If commandbatch is found
* a) store the current commandbatch fault_policy in context's next
* commandbatch fault_policy
* b) force preamble for next commandbatch
*/
for (i = 1; i < count; i++) {
if (DRAWOBJ(replay[i])->context->id == drawobj->context->id) {
replay[i]->fault_policy = replay[0]->fault_policy;
set_bit(CMDOBJ_FORCE_PREAMBLE, &replay[i]->priv);
set_bit(KGSL_FT_SKIPCMD, &replay[i]->fault_recovery);
break;
}
}
/*
* If we did not find the next cmd then
* a) set a flag for next command issued in this context
* b) store the fault_policy, this fault_policy becomes the policy of
* next command issued in this context
*/
if ((i == count) && drawctxt) {
set_bit(ADRENO_CONTEXT_SKIP_CMD, &drawctxt->base.priv);
drawctxt->fault_policy = replay[0]->fault_policy;
}
/* set the flags to skip this cmdobj */
set_bit(CMDOBJ_SKIP, &cmdobj->priv);
cmdobj->fault_recovery = 0;
}
static void _skip_frame(struct kgsl_drawobj_cmd *cmdobj,
struct kgsl_drawobj_cmd **replay, int count)
{
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
struct adreno_context *drawctxt = ADRENO_CONTEXT(drawobj->context);
int skip = 1;
int i;
for (i = 0; i < count; i++) {
struct kgsl_drawobj *replay_obj = DRAWOBJ(replay[i]);
/*
* Only operate on drawobj's that belong to the
* faulting context
*/
if (replay_obj->context->id != drawobj->context->id)
continue;
/*
* Skip all the drawobjs in this context until
* the EOF flag is seen. If the EOF flag is seen then
* force the preamble for the next command.
*/
if (skip) {
set_bit(CMDOBJ_SKIP, &replay[i]->priv);
if (replay_obj->flags & KGSL_DRAWOBJ_END_OF_FRAME)
skip = 0;
} else {
set_bit(CMDOBJ_FORCE_PREAMBLE, &replay[i]->priv);
return;
}
}
/*
* If the EOF flag hasn't been seen yet then set the flag in the
* drawctxt to keep looking for it
*/
if (skip && drawctxt)
set_bit(ADRENO_CONTEXT_SKIP_EOF, &drawctxt->base.priv);
/*
* If we did see the EOF flag then force the preamble on for the
* next command issued on this context
*/
if (!skip && drawctxt)
set_bit(ADRENO_CONTEXT_FORCE_PREAMBLE, &drawctxt->base.priv);
}
static void remove_invalidated_cmdobjs(struct kgsl_device *device,
struct kgsl_drawobj_cmd **replay, int count)
{
int i;
for (i = 0; i < count; i++) {
struct kgsl_drawobj_cmd *cmdobj = replay[i];
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
if (cmdobj == NULL)
continue;
if (kgsl_context_is_bad(drawobj->context)) {
replay[i] = NULL;
mutex_lock(&device->mutex);
kgsl_cancel_events_timestamp(device,
&drawobj->context->events, drawobj->timestamp);
mutex_unlock(&device->mutex);
kgsl_drawobj_destroy(drawobj);
}
}
}
#define pr_fault(_d, _c, fmt, args...) \
pr_context(_d, (_c)->context, fmt, ##args)
static void adreno_fault_header(struct kgsl_device *device,
struct adreno_ringbuffer *rb, struct kgsl_drawobj_cmd *cmdobj,
int fault)
{
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
struct adreno_context *drawctxt =
drawobj ? ADRENO_CONTEXT(drawobj->context) : NULL;
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
unsigned int status, rptr, wptr, ib1sz, ib2sz;
uint64_t ib1base, ib2base;
bool gx_on = adreno_gx_is_on(adreno_dev);
int id = (rb != NULL) ? rb->id : -1;
const char *type = fault & ADRENO_GMU_FAULT ? "gmu" : "gpu";
if (!gx_on) {
if (drawobj != NULL) {
pr_fault(device, drawobj,
"%s fault ctx %u ctx_type %s ts %u and GX is OFF\n",
type, drawobj->context->id,
kgsl_context_type(drawctxt->type),
drawobj->timestamp);
pr_fault(device, drawobj, "cmdline: %s\n",
drawctxt->base.proc_priv->cmdline);
} else
dev_err(device->dev, "RB[%d] : %s fault and GX is OFF\n",
id, type);
return;
}
if (gpudev->fault_header)
return gpudev->fault_header(adreno_dev, drawobj);
adreno_readreg(adreno_dev, ADRENO_REG_RBBM_STATUS, &status);
adreno_readreg(adreno_dev, ADRENO_REG_CP_RB_RPTR, &rptr);
adreno_readreg(adreno_dev, ADRENO_REG_CP_RB_WPTR, &wptr);
adreno_readreg64(adreno_dev, ADRENO_REG_CP_IB1_BASE,
ADRENO_REG_CP_IB1_BASE_HI, &ib1base);
adreno_readreg(adreno_dev, ADRENO_REG_CP_IB1_BUFSZ, &ib1sz);
adreno_readreg64(adreno_dev, ADRENO_REG_CP_IB2_BASE,
ADRENO_REG_CP_IB2_BASE_HI, &ib2base);
adreno_readreg(adreno_dev, ADRENO_REG_CP_IB2_BUFSZ, &ib2sz);
if (drawobj != NULL) {
drawctxt->base.total_fault_count++;
drawctxt->base.last_faulted_cmd_ts = drawobj->timestamp;
trace_adreno_gpu_fault(drawobj->context->id,
drawobj->timestamp,
status, rptr, wptr, ib1base, ib1sz,
ib2base, ib2sz, drawctxt->rb->id);
pr_fault(device, drawobj,
"%s fault ctx %u ctx_type %s ts %u status %8.8X rb %4.4x/%4.4x ib1 %16.16llX/%4.4x ib2 %16.16llX/%4.4x\n",
type, drawobj->context->id,
kgsl_context_type(drawctxt->type),
drawobj->timestamp, status,
rptr, wptr, ib1base, ib1sz, ib2base, ib2sz);
pr_fault(device, drawobj, "cmdline: %s\n",
drawctxt->base.proc_priv->cmdline);
if (rb != NULL)
pr_fault(device, drawobj,
"%s fault rb %d rb sw r/w %4.4x/%4.4x\n",
type, rb->id, rptr, rb->wptr);
} else {
dev_err(device->dev,
"RB[%d] : %s fault status %8.8X rb %4.4x/%4.4x ib1 %16.16llX/%4.4x ib2 %16.16llX/%4.4x\n",
id, type, status, rptr, wptr, ib1base, ib1sz, ib2base,
ib2sz);
if (rb != NULL)
dev_err(device->dev,
"RB[%d] : %s fault rb sw r/w %4.4x/%4.4x\n",
rb->id, type, rptr, rb->wptr);
}
}
void adreno_fault_skipcmd_detached(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt,
struct kgsl_drawobj *drawobj)
{
if (test_bit(ADRENO_CONTEXT_SKIP_CMD, &drawctxt->base.priv) &&
kgsl_context_detached(&drawctxt->base)) {
pr_context(KGSL_DEVICE(adreno_dev), drawobj->context,
"gpu detached context %d\n", drawobj->context->id);
clear_bit(ADRENO_CONTEXT_SKIP_CMD, &drawctxt->base.priv);
}
}
/**
* process_cmdobj_fault() - Process a cmdobj for fault policies
* @device: Device on which the cmdobj caused a fault
* @replay: List of cmdobj's that are to be replayed on the device. The
* first command in the replay list is the faulting command and the remaining
* cmdobj's in the list are commands that were submitted to the same queue
* as the faulting one.
* @count: Number of cmdobj's in replay
* @base: The IB1 base at the time of fault
* @fault: The fault type
*/
static void process_cmdobj_fault(struct kgsl_device *device,
struct kgsl_drawobj_cmd **replay, int count,
unsigned int base,
int fault)
{
struct kgsl_drawobj_cmd *cmdobj = replay[0];
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
int i;
char *state = "failed";
/*
* If GFT recovered more than X times in Y ms invalidate the context
* and do not attempt recovery.
* Example: X==3 and Y==3000 ms, GPU hung at 500ms, 1700ms, 25000ms and
* 3000ms for the same context, we will not try FT and invalidate the
* context @3000ms because context triggered GFT more than 3 times in
* last 3 seconds. If a context caused recoverable GPU hangs
* where 1st and 4th gpu hang are more than 3 seconds apart we
* won't disable GFT and invalidate the context.
*/
if (test_bit(KGSL_FT_THROTTLE, &cmdobj->fault_policy)) {
if (ktime_ms_delta(ktime_get(), drawobj->context->fault_time) >
_fault_throttle_time) {
drawobj->context->fault_time = ktime_get();
drawobj->context->fault_count = 1;
} else {
drawobj->context->fault_count++;
if (drawobj->context->fault_count >
_fault_throttle_burst) {
set_bit(KGSL_FT_DISABLE,
&cmdobj->fault_policy);
pr_context(device, drawobj->context,
"gpu fault threshold exceeded %d faults in %d msecs\n",
_fault_throttle_burst,
_fault_throttle_time);
}
}
}
/*
* If FT is disabled for this cmdobj invalidate immediately
*/
if (test_bit(KGSL_FT_DISABLE, &cmdobj->fault_policy) ||
test_bit(KGSL_FT_TEMP_DISABLE, &cmdobj->fault_policy)) {
state = "skipped";
bitmap_zero(&cmdobj->fault_policy, BITS_PER_LONG);
}
/* If the context is detached do not run FT on context */
if (kgsl_context_detached(drawobj->context)) {
state = "detached";
bitmap_zero(&cmdobj->fault_policy, BITS_PER_LONG);
}
/*
* Set a flag so we don't print another PM dump if the cmdobj fails
* again on replay
*/
set_bit(KGSL_FT_SKIP_PMDUMP, &cmdobj->fault_policy);
/*
* A hardware fault generally means something was deterministically
* wrong with the cmdobj - no point in trying to replay it
* Clear the replay bit and move on to the next policy level
*/
if (fault & ADRENO_HARD_FAULT)
clear_bit(KGSL_FT_REPLAY, &(cmdobj->fault_policy));
/*
* A timeout fault means the IB timed out - clear the policy and
* invalidate - this will clear the FT_SKIP_PMDUMP bit but that is okay
* because we won't see this cmdobj again
*/
if ((fault & ADRENO_TIMEOUT_FAULT) ||
(fault & ADRENO_CTX_DETATCH_TIMEOUT_FAULT))
bitmap_zero(&cmdobj->fault_policy, BITS_PER_LONG);
/*
* If the context had a GPU page fault then it is likely it would fault
* again if replayed
*/
if (test_bit(KGSL_CONTEXT_PRIV_PAGEFAULT,
&drawobj->context->priv)) {
/* we'll need to resume the mmu later... */
clear_bit(KGSL_FT_REPLAY, &cmdobj->fault_policy);
clear_bit(KGSL_CONTEXT_PRIV_PAGEFAULT,
&drawobj->context->priv);
}
/*
* Execute the fault tolerance policy. Each cmdobj stores the
* current fault policy that was set when it was queued.
* As the options are tried in descending priority
* (REPLAY -> SKIPIBS -> SKIPFRAME -> NOTHING) the bits are cleared
* from the cmdobj policy so the next thing can be tried if the
* change comes around again
*/
/* Replay the hanging cmdobj again */
if (test_and_clear_bit(KGSL_FT_REPLAY, &cmdobj->fault_policy)) {
trace_adreno_cmdbatch_recovery(cmdobj, BIT(KGSL_FT_REPLAY));
set_bit(KGSL_FT_REPLAY, &cmdobj->fault_recovery);
return;
}
/*
* Skip the last IB1 that was played but replay everything else.
* Note that the last IB1 might not be in the "hung" cmdobj
* because the CP may have caused a page-fault while it was prefetching
* the next IB1/IB2. walk all outstanding commands and zap the
* supposedly bad IB1 where ever it lurks.
*/
if (test_and_clear_bit(KGSL_FT_SKIPIB, &cmdobj->fault_policy)) {
trace_adreno_cmdbatch_recovery(cmdobj, BIT(KGSL_FT_SKIPIB));
set_bit(KGSL_FT_SKIPIB, &cmdobj->fault_recovery);
for (i = 0; i < count; i++) {
if (replay[i] != NULL &&
DRAWOBJ(replay[i])->context->id ==
drawobj->context->id)
_skip_ib(replay[i], base);
}
return;
}
/* Skip the faulted cmdobj submission */
if (test_and_clear_bit(KGSL_FT_SKIPCMD, &cmdobj->fault_policy)) {
trace_adreno_cmdbatch_recovery(cmdobj, BIT(KGSL_FT_SKIPCMD));
/* Skip faulting cmdobj */
_skip_cmd(cmdobj, replay, count);
return;
}
if (test_and_clear_bit(KGSL_FT_SKIPFRAME, &cmdobj->fault_policy)) {
trace_adreno_cmdbatch_recovery(cmdobj,
BIT(KGSL_FT_SKIPFRAME));
set_bit(KGSL_FT_SKIPFRAME, &cmdobj->fault_recovery);
/*
* Skip all the pending cmdobj's for this context until
* the EOF frame is seen
*/
_skip_frame(cmdobj, replay, count);
return;
}
/* If we get here then all the policies failed */
pr_context(device, drawobj->context, "gpu %s ctx %d ts %u\n",
state, drawobj->context->id, drawobj->timestamp);
/* Mark the context as failed and invalidate it */
adreno_drawctxt_set_guilty(device, drawobj->context);
}
/**
* recover_dispatch_q() - Recover all commands in a dispatch queue by
* resubmitting the commands
* @device: Device on which recovery is performed
* @dispatch_q: The command queue to recover
* @fault: Faults caused by the command in the dispatch q
* @base: The IB1 base during the fault
*/
static void recover_dispatch_q(struct kgsl_device *device,
struct adreno_dispatcher_drawqueue *dispatch_q,
int fault,
unsigned int base)
{
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
struct kgsl_drawobj_cmd **replay;
unsigned int ptr;
int first = 0;
int count = 0;
int i;
/* Allocate memory to store the inflight commands */
replay = kcalloc(dispatch_q->inflight, sizeof(*replay), GFP_KERNEL);
if (replay == NULL) {
unsigned int ptr = dispatch_q->head;
/* Recovery failed - mark everybody on this q guilty */
while (ptr != dispatch_q->tail) {
struct kgsl_drawobj_cmd *cmdobj =
dispatch_q->cmd_q[ptr];
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
adreno_drawctxt_set_guilty(device, drawobj->context);
kgsl_drawobj_destroy(drawobj);
ptr = DRAWQUEUE_NEXT(ptr,
ADRENO_DISPATCH_DRAWQUEUE_SIZE);
}
/*
* Set the replay count to zero - this will ensure that the
* hardware gets reset but nothing else gets played
*/
count = 0;
goto replay;
}
/* Copy the inflight cmdobj's into the temporary storage */
ptr = dispatch_q->head;
while (ptr != dispatch_q->tail) {
replay[count++] = dispatch_q->cmd_q[ptr];
ptr = DRAWQUEUE_NEXT(ptr, ADRENO_DISPATCH_DRAWQUEUE_SIZE);
}
if (fault && count)
process_cmdobj_fault(device, replay,
count, base, fault);
replay:
dispatch_q->inflight = 0;
dispatch_q->head = dispatch_q->tail = 0;
/* Remove any pending cmdobj's that have been invalidated */
remove_invalidated_cmdobjs(device, replay, count);
/* Replay the pending command buffers */
for (i = 0; i < count; i++) {
int ret;
if (replay[i] == NULL)
continue;
/*
* Force the preamble on the first command (if applicable) to
* avoid any strange stage issues
*/
if (first == 0) {
set_bit(CMDOBJ_FORCE_PREAMBLE, &replay[i]->priv);
first = 1;
}
/*
* Force each cmdobj to wait for idle - this avoids weird
* CP parse issues
*/
set_bit(CMDOBJ_WFI, &replay[i]->priv);
ret = sendcmd(adreno_dev, replay[i]);
/*
* If sending the command fails, then try to recover by
* invalidating the context
*/
if (ret) {
pr_context(device, replay[i]->base.context,
"gpu reset failed ctx %u ts %u\n",
replay[i]->base.context->id,
replay[i]->base.timestamp);
/* Mark this context as guilty (failed recovery) */
adreno_drawctxt_set_guilty(device, replay[i]->base.context);
remove_invalidated_cmdobjs(device, &replay[i],
count - i);
}
}
/* Clear the fault bit */
clear_bit(ADRENO_DEVICE_FAULT, &adreno_dev->priv);
kfree(replay);
}
static void do_header_and_snapshot(struct kgsl_device *device, int fault,
struct adreno_ringbuffer *rb, struct kgsl_drawobj_cmd *cmdobj)
{
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
/* Always dump the snapshot on a non-drawobj failure */
if (cmdobj == NULL) {
adreno_fault_header(device, rb, NULL, fault);
/* GMU snapshot will also pull a full device snapshot */
if (fault & ADRENO_GMU_FAULT)
gmu_core_fault_snapshot(device, GMU_FAULT_PANIC_NONE);
else
kgsl_device_snapshot(device, NULL, NULL, false);
return;
}
/* Skip everything if the PMDUMP flag is set */
if (test_bit(KGSL_FT_SKIP_PMDUMP, &cmdobj->fault_policy))
return;
/* Print the fault header */
adreno_fault_header(device, rb, cmdobj, fault);
if (!(drawobj->context->flags & KGSL_CONTEXT_NO_SNAPSHOT))
kgsl_device_snapshot(device, drawobj->context, NULL,
fault & ADRENO_GMU_FAULT);
}
static int dispatcher_do_fault(struct adreno_device *adreno_dev)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
struct adreno_dispatcher_drawqueue *dispatch_q = NULL, *dispatch_q_temp;
struct adreno_ringbuffer *rb;
struct adreno_ringbuffer *hung_rb = NULL;
unsigned int reg;
uint64_t base = 0;
struct kgsl_drawobj_cmd *cmdobj = NULL;
int ret, i;
int fault;
int halt;
bool gx_on;
fault = adreno_gpu_fault(adreno_dev);
if (fault == 0)
return 0;
/*
* Return early if there is a concurrent suspend in progress. The suspend thread will error
* out in the presence of this hwsched fault and requeue the dispatcher to handle this fault
*/
if (!mutex_trylock(&adreno_dev->fault_recovery_mutex))
return 1;
/*
* Wait long enough to allow the system to come out of suspend completely, which can take
* variable amount of time especially if it has to rewind suspend processes and devices.
*/
if (!wait_for_completion_timeout(&adreno_dev->suspend_recovery_gate,
msecs_to_jiffies(ADRENO_SUSPEND_RECOVERY_GATE_TIMEOUT_MS))) {
dev_err(device->dev, "suspend recovery gate timeout\n");
adreno_scheduler_queue(adreno_dev);
mutex_unlock(&adreno_dev->fault_recovery_mutex);
return 1;
}
mutex_lock(&device->mutex);
/*
* In the very unlikely case that the power is off, do nothing - the
* state will be reset on power up and everybody will be happy
*/
if (!kgsl_state_is_awake(device)) {
mutex_unlock(&device->mutex);
mutex_unlock(&adreno_dev->fault_recovery_mutex);
return 0;
}
/* Mask all GMU interrupts */
if (gmu_core_isenabled(device)) {
adreno_write_gmureg(adreno_dev,
ADRENO_REG_GMU_AO_HOST_INTERRUPT_MASK,
0xFFFFFFFF);
adreno_write_gmureg(adreno_dev,
ADRENO_REG_GMU_GMU2HOST_INTR_MASK,
0xFFFFFFFF);
}
gx_on = adreno_gx_is_on(adreno_dev);
/*
* Check if this function was entered after a pagefault. If so, only
* proceed if the fault handler has already run in the IRQ thread,
* else return early to give the fault handler a chance to run.
*/
if (!(fault & ADRENO_IOMMU_STALL_ON_PAGE_FAULT) && gx_on) {
if (adreno_smmu_is_stalled(adreno_dev)) {
mutex_unlock(&device->mutex);
mutex_unlock(&adreno_dev->fault_recovery_mutex);
dev_err(device->dev,
"SMMU is stalled without a pagefault\n");
return -EBUSY;
}
}
/* Turn off all the timers */
del_timer_sync(&dispatcher->timer);
/*
* Deleting uninitialized timer will block for ever on kernel debug
* disable build. Hence skip del timer if it is not initialized.
*/
if (adreno_is_preemption_enabled(adreno_dev))
del_timer_sync(&adreno_dev->preempt.timer);
if (gx_on)
adreno_readreg64(adreno_dev, ADRENO_REG_CP_RB_BASE,
ADRENO_REG_CP_RB_BASE_HI, &base);
/*
* Force the CP off for anything but a hard fault to make sure it is
* good and stopped
*/
if (!(fault & ADRENO_HARD_FAULT) && gx_on) {
adreno_readreg(adreno_dev, ADRENO_REG_CP_ME_CNTL, &reg);
if (adreno_is_a5xx(adreno_dev) || adreno_is_a6xx(adreno_dev))
reg |= 1 | (1 << 1);
else
reg = 0x0;
adreno_writereg(adreno_dev, ADRENO_REG_CP_ME_CNTL, reg);
}
/*
* retire cmdobj's from all the dispatch_q's before starting recovery
*/
FOR_EACH_RINGBUFFER(adreno_dev, rb, i) {
adreno_dispatch_retire_drawqueue(adreno_dev,
&(rb->dispatch_q));
/* Select the active dispatch_q */
if (base == rb->buffer_desc->gpuaddr) {
dispatch_q = &(rb->dispatch_q);
hung_rb = rb;
if (adreno_dev->cur_rb != hung_rb) {
adreno_dev->prev_rb = adreno_dev->cur_rb;
adreno_dev->cur_rb = hung_rb;
}
}
}
if (dispatch_q && !adreno_drawqueue_is_empty(dispatch_q)) {
cmdobj = dispatch_q->cmd_q[dispatch_q->head];
trace_adreno_cmdbatch_fault(cmdobj, fault);
}
if (gx_on)
adreno_readreg64(adreno_dev, ADRENO_REG_CP_IB1_BASE,
ADRENO_REG_CP_IB1_BASE_HI, &base);
if (!test_bit(KGSL_FT_PAGEFAULT_GPUHALT_ENABLE, &device->mmu.pfpolicy)
&& adreno_dev->cooperative_reset)
gmu_core_dev_cooperative_reset(device);
if (!(fault & ADRENO_GMU_FAULT_SKIP_SNAPSHOT))
do_header_and_snapshot(device, fault, hung_rb, cmdobj);
adreno_gpufault_stats(adreno_dev, cmdobj ? DRAWOBJ(cmdobj) : NULL, NULL, fault);
/* Turn off the KEEPALIVE vote from the ISR for hard fault */
if (gpudev->gpu_keepalive && fault & ADRENO_HARD_FAULT)
gpudev->gpu_keepalive(adreno_dev, false);
/* Terminate the stalled transaction and resume the IOMMU */
if (fault & ADRENO_IOMMU_STALL_ON_PAGE_FAULT)
kgsl_mmu_pagefault_resume(&device->mmu, true);
/* Reset the dispatcher queue */
dispatcher->inflight = 0;
/* Remove the bus hint */
device->pwrctrl.rt_bus_hint_active = false;
/* Reset the GPU and make sure halt is not set during recovery */
halt = adreno_gpu_halt(adreno_dev);
adreno_clear_gpu_halt(adreno_dev);
/*
* If there is a stall in the ringbuffer after all commands have been
* retired then we could hit problems if contexts are waiting for
* internal timestamps that will never retire
*/
if (hung_rb != NULL) {
kgsl_sharedmem_writel(device->memstore,
MEMSTORE_RB_OFFSET(hung_rb, soptimestamp),
hung_rb->timestamp);
kgsl_sharedmem_writel(device->memstore,
MEMSTORE_RB_OFFSET(hung_rb, eoptimestamp),
hung_rb->timestamp);
/* Schedule any pending events to be run */
kgsl_process_event_group(device, &hung_rb->events);
}
ret = adreno_reset(device, fault);
/* If adreno_reset() fails then what hope do we have for the future? */
BUG_ON(ret);
/* if any other fault got in until reset then ignore */
adreno_clear_gpu_fault(adreno_dev);
mutex_unlock(&device->mutex);
mutex_unlock(&adreno_dev->fault_recovery_mutex);
/* recover all the dispatch_q's starting with the one that hung */
if (dispatch_q)
recover_dispatch_q(device, dispatch_q, fault, base);
FOR_EACH_RINGBUFFER(adreno_dev, rb, i) {
dispatch_q_temp = &(rb->dispatch_q);
if (dispatch_q_temp != dispatch_q)
recover_dispatch_q(device, dispatch_q_temp, 0, base);
}
atomic_add(halt, &adreno_dev->halt);
return 1;
}
static inline int drawobj_consumed(struct kgsl_drawobj *drawobj,
unsigned int consumed, unsigned int retired)
{
return ((timestamp_cmp(drawobj->timestamp, consumed) >= 0) &&
(timestamp_cmp(retired, drawobj->timestamp) < 0));
}
static const char *_ft_type(enum kgsl_ft_policy_bits nr)
{
if (nr == KGSL_FT_OFF)
return "off";
else if (nr == KGSL_FT_REPLAY)
return "replay";
else if (nr == KGSL_FT_SKIPIB)
return "skipib";
else if (nr == KGSL_FT_SKIPFRAME)
return "skipfame";
else if (nr == KGSL_FT_DISABLE)
return "disable";
else if (nr == KGSL_FT_TEMP_DISABLE)
return "temp";
else if (nr == KGSL_FT_THROTTLE)
return "throttle";
else if (nr == KGSL_FT_SKIPCMD)
return "skipcmd";
return "";
}
static void _print_recovery(struct kgsl_device *device,
struct kgsl_drawobj_cmd *cmdobj)
{
int nr = find_first_bit(&cmdobj->fault_recovery, BITS_PER_LONG);
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
pr_context(device, drawobj->context,
"gpu %s ctx %u ts %u policy %lX\n",
_ft_type(nr), drawobj->context->id, drawobj->timestamp,
cmdobj->fault_recovery);
}
static void cmdobj_profile_ticks(struct adreno_device *adreno_dev,
struct kgsl_drawobj_cmd *cmdobj, uint64_t *start, uint64_t *retire,
uint64_t *active)
{
void *ptr = adreno_dev->profile_buffer->hostptr;
struct adreno_drawobj_profile_entry *entry;
entry = (struct adreno_drawobj_profile_entry *)
(ptr + (cmdobj->profile_index * sizeof(*entry)));
/* get updated values of started and retired */
rmb();
*start = entry->started;
*retire = entry->retired;
if (ADRENO_GPUREV(adreno_dev) < 600)
*active = entry->retired - entry->started;
else
*active = entry->ctx_end - entry->ctx_start;
}
static void retire_cmdobj(struct adreno_device *adreno_dev,
struct kgsl_drawobj_cmd *cmdobj)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
struct adreno_context *drawctxt = ADRENO_CONTEXT(drawobj->context);
struct adreno_ringbuffer *rb = drawctxt->rb;
struct kgsl_context *context = drawobj->context;
uint64_t start = 0, end = 0, active = 0;
struct retire_info info = {0};
if (cmdobj->fault_recovery != 0) {
set_bit(ADRENO_CONTEXT_FAULT, &drawobj->context->priv);
_print_recovery(KGSL_DEVICE(adreno_dev), cmdobj);
}
if (test_bit(CMDOBJ_PROFILE, &cmdobj->priv))
cmdobj_profile_ticks(adreno_dev, cmdobj, &start, &end, &active);
info.inflight = (int)dispatcher->inflight;
info.rb_id = rb->id;
info.wptr = rb->wptr;
info.timestamp = drawobj->timestamp;
info.sop = start;
info.eop = end;
info.active = active;
/* protected GPU work must not be reported */
if (!(context->flags & KGSL_CONTEXT_SECURE))
kgsl_work_period_update(KGSL_DEVICE(adreno_dev),
context->proc_priv->period, active);
msm_perf_events_update(MSM_PERF_GFX, MSM_PERF_RETIRED,
pid_nr(context->proc_priv->pid),
context->id, drawobj->timestamp,
!!(drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME));
if (drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME) {
atomic64_inc(&context->proc_priv->frame_count);
atomic_inc(&context->proc_priv->period->frames);
}
info.rptr = adreno_get_rptr(rb);
trace_adreno_cmdbatch_retired(drawobj->context, &info,
drawobj->flags, rb->dispatch_q.inflight,
cmdobj->fault_recovery);
log_kgsl_cmdbatch_retired_event(context->id, drawobj->timestamp,
context->priority, drawobj->flags, start, end);
drawctxt->submit_retire_ticks[drawctxt->ticks_index] =
end - cmdobj->submit_ticks;
drawctxt->ticks_index = (drawctxt->ticks_index + 1) %
SUBMIT_RETIRE_TICKS_SIZE;
trace_adreno_cmdbatch_done(drawobj->context->id,
drawobj->context->priority, drawobj->timestamp);
kgsl_drawobj_destroy(drawobj);
}
static int adreno_dispatch_retire_drawqueue(struct adreno_device *adreno_dev,
struct adreno_dispatcher_drawqueue *drawqueue)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
int count = 0;
while (!adreno_drawqueue_is_empty(drawqueue)) {
struct kgsl_drawobj_cmd *cmdobj =
drawqueue->cmd_q[drawqueue->head];
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
if (!kgsl_check_timestamp(device, drawobj->context,
drawobj->timestamp))
break;
retire_cmdobj(adreno_dev, cmdobj);
dispatcher->inflight--;
drawqueue->inflight--;
drawqueue->cmd_q[drawqueue->head] = NULL;
drawqueue->head = DRAWQUEUE_NEXT(drawqueue->head,
ADRENO_DISPATCH_DRAWQUEUE_SIZE);
count++;
}
return count;
}
static void _adreno_dispatch_check_timeout(struct adreno_device *adreno_dev,
struct adreno_dispatcher_drawqueue *drawqueue)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct kgsl_drawobj *drawobj =
DRAWOBJ(drawqueue->cmd_q[drawqueue->head]);
/* Don't timeout if the timer hasn't expired yet (duh) */
if (time_is_after_jiffies(drawqueue->expires))
return;
/* Don't timeout if the IB timeout is disabled globally */
if (!adreno_long_ib_detect(adreno_dev))
return;
/* Don't time out if the context has disabled it */
if (drawobj->context->flags & KGSL_CONTEXT_NO_FAULT_TOLERANCE)
return;
pr_context(device, drawobj->context, "gpu timeout ctx %u ts %u\n",
drawobj->context->id, drawobj->timestamp);
adreno_set_gpu_fault(adreno_dev, ADRENO_TIMEOUT_FAULT);
/*
* This makes sure dispatcher doesn't run endlessly in cases where
* we couldn't run recovery
*/
drawqueue->expires = jiffies + msecs_to_jiffies(adreno_drawobj_timeout);
}
static int adreno_dispatch_process_drawqueue(struct adreno_device *adreno_dev,
struct adreno_dispatcher_drawqueue *drawqueue)
{
int count = adreno_dispatch_retire_drawqueue(adreno_dev, drawqueue);
/* Nothing to do if there are no pending commands */
if (adreno_drawqueue_is_empty(drawqueue))
return count;
/* Don't update the drawqueue timeout if it isn't active */
if (!drawqueue_is_current(drawqueue))
return count;
/*
* If the current ringbuffer retired any commands then universally
* reset the timeout
*/
if (count) {
drawqueue->expires = jiffies +
msecs_to_jiffies(adreno_drawobj_timeout);
return count;
}
/*
* If we get here then 1) the ringbuffer is current and 2) we haven't
* retired anything. Check to see if the timeout if valid for the
* current drawobj and fault if it has expired
*/
_adreno_dispatch_check_timeout(adreno_dev, drawqueue);
return 0;
}
/* Take down the dispatcher and release any power states */
static void _dispatcher_power_down(struct adreno_device *adreno_dev)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
mutex_lock(&device->mutex);
if (test_and_clear_bit(ADRENO_DISPATCHER_ACTIVE, &dispatcher->priv))
complete_all(&dispatcher->idle_gate);
process_rt_bus_hint(device, false);
if (test_bit(ADRENO_DISPATCHER_POWER, &dispatcher->priv)) {
adreno_active_count_put(adreno_dev);
clear_bit(ADRENO_DISPATCHER_POWER, &dispatcher->priv);
}
mutex_unlock(&device->mutex);
}
static void adreno_dispatcher_work(struct kthread_work *work)
{
struct adreno_device *adreno_dev =
container_of(work, struct adreno_device, scheduler_work);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
int count = 0;
unsigned int i = 0;
mutex_lock(&dispatcher->mutex);
/*
* As long as there are inflight commands, process retired comamnds from
* all drawqueues
*/
for (i = 0; i < adreno_dev->num_ringbuffers; i++) {
struct adreno_dispatcher_drawqueue *drawqueue =
DRAWQUEUE(&adreno_dev->ringbuffers[i]);
count += adreno_dispatch_process_drawqueue(adreno_dev,
drawqueue);
if (dispatcher->inflight == 0)
break;
}
kgsl_process_event_groups(device);
/*
* dispatcher_do_fault() returns 0 if no faults occurred. If that is the
* case, then clean up preemption and try to schedule more work
*/
if (dispatcher_do_fault(adreno_dev) == 0) {
/* Clean up after preemption */
if (gpudev->preemption_schedule)
gpudev->preemption_schedule(adreno_dev);
/* Run the scheduler for to dispatch new commands */
_adreno_dispatcher_issuecmds(adreno_dev);
}
/*
* If there are commands pending, update the timers, otherwise release
* the power state to prepare for power down
*/
if (dispatcher->inflight > 0)
_dispatcher_update_timers(adreno_dev);
else
_dispatcher_power_down(adreno_dev);
mutex_unlock(&dispatcher->mutex);
}
/*
* Put a draw context on the dispatcher pending queue and schedule the
* dispatcher. This is used to reschedule changes that might have been blocked
* for sync points or other concerns
*/
static void adreno_dispatcher_queue_context(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt)
{
dispatcher_queue_context(adreno_dev, drawctxt);
adreno_scheduler_queue(adreno_dev);
}
/*
* This is called when the timer expires - it either means the GPU is hung or
* the IB is taking too long to execute
*/
static void adreno_dispatcher_timer(struct timer_list *t)
{
struct adreno_dispatcher *dispatcher = from_timer(dispatcher, t, timer);
struct adreno_device *adreno_dev = container_of(dispatcher,
struct adreno_device, dispatcher);
adreno_scheduler_queue(adreno_dev);
}
/**
* adreno_dispatcher_start() - activate the dispatcher
* @adreno_dev: pointer to the adreno device structure
*
*/
void adreno_dispatcher_start(struct kgsl_device *device)
{
complete_all(&device->halt_gate);
/* Schedule the work loop to get things going */
adreno_scheduler_queue(ADRENO_DEVICE(device));
}
/**
* adreno_dispatcher_stop() - stop the dispatcher
* @adreno_dev: pointer to the adreno device structure
*
* Stop the dispatcher and close all the timers
*/
void adreno_dispatcher_stop(struct adreno_device *adreno_dev)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
del_timer_sync(&dispatcher->timer);
}
/* Return the ringbuffer that matches the draw context priority */
static struct adreno_ringbuffer *dispatch_get_rb(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt)
{
int level;
/* If preemption is disabled everybody goes on the same ringbuffer */
if (!adreno_is_preemption_enabled(adreno_dev))
return &adreno_dev->ringbuffers[0];
/*
* Math to convert the priority field in context structure to an RB ID.
* Divide up the context priority based on number of ringbuffer levels.
*/
level = min_t(int, drawctxt->base.priority / adreno_dev->num_ringbuffers,
adreno_dev->num_ringbuffers - 1);
return &adreno_dev->ringbuffers[level];
}
static void adreno_dispatcher_setup_context(struct adreno_device *adreno_dev,
struct adreno_context *drawctxt)
{
drawctxt->rb = dispatch_get_rb(adreno_dev, drawctxt);
}
static void change_preemption(struct adreno_device *adreno_dev, void *priv)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct kgsl_context *context;
struct adreno_context *drawctxt;
struct adreno_ringbuffer *rb;
int id, i, ret;
/* Make sure all ringbuffers are finished */
FOR_EACH_RINGBUFFER(adreno_dev, rb, i) {
ret = adreno_ringbuffer_waittimestamp(rb, rb->timestamp,
2 * 1000);
if (ret) {
dev_err(device->dev,
"Cannot disable preemption because couldn't idle ringbuffer[%d] ret: %d\n",
rb->id, ret);
return;
}
}
change_bit(ADRENO_DEVICE_PREEMPTION, &adreno_dev->priv);
adreno_dev->cur_rb = &adreno_dev->ringbuffers[0];
adreno_dev->next_rb = NULL;
adreno_dev->prev_rb = NULL;
/* Update the ringbuffer for each draw context */
write_lock(&device->context_lock);
idr_for_each_entry(&device->context_idr, context, id) {
drawctxt = ADRENO_CONTEXT(context);
drawctxt->rb = dispatch_get_rb(adreno_dev, drawctxt);
/*
* Make sure context destroy checks against the correct
* ringbuffer's timestamp.
*/
adreno_rb_readtimestamp(adreno_dev, drawctxt->rb,
KGSL_TIMESTAMP_RETIRED, &drawctxt->internal_timestamp);
}
write_unlock(&device->context_lock);
}
static int _preemption_store(struct adreno_device *adreno_dev, bool val)
{
if (!adreno_preemption_feature_set(adreno_dev) ||
(test_bit(ADRENO_DEVICE_PREEMPTION, &adreno_dev->priv) == val))
return 0;
return adreno_power_cycle(adreno_dev, change_preemption, NULL);
}
static bool _preemption_show(struct adreno_device *adreno_dev)
{
return adreno_is_preemption_enabled(adreno_dev);
}
static unsigned int _preempt_count_show(struct adreno_device *adreno_dev)
{
return adreno_dev->preempt.count;
}
static int _ft_long_ib_detect_store(struct adreno_device *adreno_dev, bool val)
{
adreno_dev->long_ib_detect = val ? true : false;
return 0;
}
static bool _ft_long_ib_detect_show(struct adreno_device *adreno_dev)
{
return adreno_dev->long_ib_detect;
}
static ADRENO_SYSFS_BOOL(preemption);
static ADRENO_SYSFS_RO_U32(preempt_count);
static ADRENO_SYSFS_BOOL(ft_long_ib_detect);
static const struct attribute *_dispatch_attr_list[] = {
&adreno_attr_preemption.attr.attr,
&adreno_attr_preempt_count.attr.attr,
&adreno_attr_ft_long_ib_detect.attr.attr,
NULL,
};
static void adreno_dispatcher_close(struct adreno_device *adreno_dev)
{
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
int i;
struct adreno_ringbuffer *rb;
mutex_lock(&dispatcher->mutex);
del_timer_sync(&dispatcher->timer);
FOR_EACH_RINGBUFFER(adreno_dev, rb, i) {
struct adreno_dispatcher_drawqueue *dispatch_q =
&(rb->dispatch_q);
while (!adreno_drawqueue_is_empty(dispatch_q)) {
kgsl_drawobj_destroy(
DRAWOBJ(dispatch_q->cmd_q[dispatch_q->head]));
dispatch_q->head = (dispatch_q->head + 1)
% ADRENO_DISPATCH_DRAWQUEUE_SIZE;
}
}
mutex_unlock(&dispatcher->mutex);
kthread_destroy_worker(adreno_dev->scheduler_worker);
adreno_set_dispatch_ops(adreno_dev, NULL);
kobject_put(&dispatcher->kobj);
kmem_cache_destroy(jobs_cache);
clear_bit(ADRENO_DISPATCHER_INIT, &dispatcher->priv);
}
struct dispatcher_attribute {
struct attribute attr;
ssize_t (*show)(struct adreno_dispatcher *dispatcher,
struct dispatcher_attribute *attr, char *buf);
ssize_t (*store)(struct adreno_dispatcher *dispatcher,
struct dispatcher_attribute *attr, const char *buf,
size_t count);
unsigned int max;
unsigned int *value;
};
#define DISPATCHER_UINT_ATTR(_name, _mode, _max, _value) \
struct dispatcher_attribute dispatcher_attr_##_name = { \
.attr = { .name = __stringify(_name), .mode = _mode }, \
.show = _show_uint, \
.store = _store_uint, \
.max = _max, \
.value = &(_value), \
}
#define to_dispatcher_attr(_a) \
container_of((_a), struct dispatcher_attribute, attr)
#define to_dispatcher(k) container_of(k, struct adreno_dispatcher, kobj)
static ssize_t _store_uint(struct adreno_dispatcher *dispatcher,
struct dispatcher_attribute *attr,
const char *buf, size_t size)
{
unsigned int val = 0;
int ret;
ret = kstrtou32(buf, 0, &val);
if (ret)
return ret;
if (!val || (attr->max && (val > attr->max)))
return -EINVAL;
*((unsigned int *) attr->value) = val;
return size;
}
static ssize_t _show_uint(struct adreno_dispatcher *dispatcher,
struct dispatcher_attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%u\n",
*((unsigned int *) attr->value));
}
static DISPATCHER_UINT_ATTR(inflight, 0644, ADRENO_DISPATCH_DRAWQUEUE_SIZE,
_dispatcher_q_inflight_hi);
static DISPATCHER_UINT_ATTR(inflight_low_latency, 0644,
ADRENO_DISPATCH_DRAWQUEUE_SIZE, _dispatcher_q_inflight_lo);
/*
* Our code that "puts back" a command from the context is much cleaner
* if we are sure that there will always be enough room in the
* ringbuffer so restrict the maximum size of the context queue to
* ADRENO_CONTEXT_DRAWQUEUE_SIZE - 1
*/
static DISPATCHER_UINT_ATTR(context_drawqueue_size, 0644,
ADRENO_CONTEXT_DRAWQUEUE_SIZE - 1, _context_drawqueue_size);
static DISPATCHER_UINT_ATTR(context_burst_count, 0644, 0,
_context_drawobj_burst);
static DISPATCHER_UINT_ATTR(drawobj_timeout, 0644, 0,
adreno_drawobj_timeout);
static DISPATCHER_UINT_ATTR(context_queue_wait, 0644, 0, _context_queue_wait);
static DISPATCHER_UINT_ATTR(fault_detect_interval, 0644, 0,
_fault_timer_interval);
static DISPATCHER_UINT_ATTR(fault_throttle_time, 0644, 0,
_fault_throttle_time);
static DISPATCHER_UINT_ATTR(fault_throttle_burst, 0644, 0,
_fault_throttle_burst);
static struct attribute *dispatcher_attrs[] = {
&dispatcher_attr_inflight.attr,
&dispatcher_attr_inflight_low_latency.attr,
&dispatcher_attr_context_drawqueue_size.attr,
&dispatcher_attr_context_burst_count.attr,
&dispatcher_attr_drawobj_timeout.attr,
&dispatcher_attr_context_queue_wait.attr,
&dispatcher_attr_fault_detect_interval.attr,
&dispatcher_attr_fault_throttle_time.attr,
&dispatcher_attr_fault_throttle_burst.attr,
NULL,
};
ATTRIBUTE_GROUPS(dispatcher);
static ssize_t dispatcher_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct adreno_dispatcher *dispatcher = to_dispatcher(kobj);
struct dispatcher_attribute *pattr = to_dispatcher_attr(attr);
ssize_t ret = -EIO;
if (pattr->show)
ret = pattr->show(dispatcher, pattr, buf);
return ret;
}
static ssize_t dispatcher_sysfs_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t count)
{
struct adreno_dispatcher *dispatcher = to_dispatcher(kobj);
struct dispatcher_attribute *pattr = to_dispatcher_attr(attr);
ssize_t ret = -EIO;
if (pattr->store)
ret = pattr->store(dispatcher, pattr, buf, count);
return ret;
}
static const struct sysfs_ops dispatcher_sysfs_ops = {
.show = dispatcher_sysfs_show,
.store = dispatcher_sysfs_store
};
static struct kobj_type ktype_dispatcher = {
.sysfs_ops = &dispatcher_sysfs_ops,
.default_groups = dispatcher_groups,
};
static const struct adreno_dispatch_ops swsched_ops = {
.close = adreno_dispatcher_close,
.queue_cmds = adreno_dispatcher_queue_cmds,
.setup_context = adreno_dispatcher_setup_context,
.queue_context = adreno_dispatcher_queue_context,
};
/**
* adreno_dispatcher_init() - Initialize the dispatcher
* @adreno_dev: pointer to the adreno device structure
*
* Initialize the dispatcher
*/
int adreno_dispatcher_init(struct adreno_device *adreno_dev)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
int ret, i;
if (test_bit(ADRENO_DISPATCHER_INIT, &dispatcher->priv))
return 0;
ret = kobject_init_and_add(&dispatcher->kobj, &ktype_dispatcher,
&device->dev->kobj, "dispatch");
if (ret)
return ret;
adreno_dev->scheduler_worker = kthread_create_worker(0, "kgsl_dispatcher");
if (IS_ERR(adreno_dev->scheduler_worker)) {
kobject_put(&dispatcher->kobj);
return PTR_ERR(adreno_dev->scheduler_worker);
}
WARN_ON(sysfs_create_files(&device->dev->kobj, _dispatch_attr_list));
mutex_init(&dispatcher->mutex);
timer_setup(&dispatcher->timer, adreno_dispatcher_timer, 0);
kthread_init_work(&adreno_dev->scheduler_work, adreno_dispatcher_work);
init_completion(&dispatcher->idle_gate);
complete_all(&dispatcher->idle_gate);
jobs_cache = KMEM_CACHE(adreno_dispatch_job, 0);
for (i = 0; i < ARRAY_SIZE(dispatcher->jobs); i++) {
init_llist_head(&dispatcher->jobs[i]);
init_llist_head(&dispatcher->requeue[i]);
}
adreno_set_dispatch_ops(adreno_dev, &swsched_ops);
sched_set_fifo(adreno_dev->scheduler_worker->task);
set_bit(ADRENO_DISPATCHER_INIT, &dispatcher->priv);
return 0;
}
/*
* adreno_dispatcher_idle() - Wait for dispatcher to idle
* @adreno_dev: Adreno device whose dispatcher needs to idle
*
* Signal dispatcher to stop sending more commands and complete
* the commands that have already been submitted. This function
* should not be called when dispatcher mutex is held.
* The caller must hold the device mutex.
*/
int adreno_dispatcher_idle(struct adreno_device *adreno_dev)
{
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
int ret;
if (device->state != KGSL_STATE_ACTIVE)
return 0;
/*
* Ensure that this function is not called when dispatcher
* mutex is held and device is started
*/
if (WARN_ON(mutex_is_locked(&dispatcher->mutex)))
return -EDEADLK;
adreno_get_gpu_halt(adreno_dev);
mutex_unlock(&device->mutex);
/*
* Flush the worker to make sure all executing
* or pending dispatcher works on worker are
* finished
*/
kthread_flush_worker(adreno_dev->scheduler_worker);
if (adreno_gpu_fault(adreno_dev) != 0) {
ret = -EDEADLK;
} else {
ret = wait_for_completion_timeout(&dispatcher->idle_gate,
msecs_to_jiffies(ADRENO_IDLE_TIMEOUT));
if (ret == 0) {
ret = -ETIMEDOUT;
WARN(1, "Dispatcher halt timeout\n");
} else if (ret < 0) {
dev_err(device->dev, "Dispatcher halt failed %d\n", ret);
} else {
ret = 0;
}
if (adreno_gpu_fault(adreno_dev) != 0)
ret = -EDEADLK;
}
mutex_lock(&device->mutex);
adreno_put_gpu_halt(adreno_dev);
/*
* requeue dispatcher work to resubmit pending commands
* that may have been blocked due to this idling request
*/
adreno_scheduler_queue(adreno_dev);
return ret;
}