root/drivers/gpu/drm/i915/gt/selftest_engine_heartbeat.c
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2018 Intel Corporation
 */

#include <linux/sort.h>

#include "i915_drv.h"

#include "intel_gt_requests.h"
#include "i915_selftest.h"
#include "selftest_engine_heartbeat.h"

static void reset_heartbeat(struct intel_engine_cs *engine)
{
        intel_engine_set_heartbeat(engine,
                                   engine->defaults.heartbeat_interval_ms);
}

static int timeline_sync(struct intel_timeline *tl)
{
        struct dma_fence *fence;
        long timeout;

        fence = i915_active_fence_get(&tl->last_request);
        if (!fence)
                return 0;

        timeout = dma_fence_wait_timeout(fence, true, HZ / 2);
        dma_fence_put(fence);
        if (timeout < 0)
                return timeout;

        return 0;
}

static int engine_sync_barrier(struct intel_engine_cs *engine)
{
        return timeline_sync(engine->kernel_context->timeline);
}

struct pulse {
        struct i915_active active;
        struct kref kref;
};

static int pulse_active(struct i915_active *active)
{
        kref_get(&container_of(active, struct pulse, active)->kref);
        return 0;
}

static void pulse_free(struct kref *kref)
{
        struct pulse *p = container_of(kref, typeof(*p), kref);

        i915_active_fini(&p->active);
        kfree(p);
}

static void pulse_put(struct pulse *p)
{
        kref_put(&p->kref, pulse_free);
}

static void pulse_retire(struct i915_active *active)
{
        pulse_put(container_of(active, struct pulse, active));
}

static struct pulse *pulse_create(void)
{
        struct pulse *p;

        p = kmalloc_obj(*p);
        if (!p)
                return p;

        kref_init(&p->kref);
        i915_active_init(&p->active, pulse_active, pulse_retire, 0);

        return p;
}

static void pulse_unlock_wait(struct pulse *p)
{
        wait_var_event_timeout(&p->active, i915_active_is_idle(&p->active), HZ);
}

static int __live_idle_pulse(struct intel_engine_cs *engine,
                             int (*fn)(struct intel_engine_cs *cs))
{
        struct pulse *p;
        int err;

        GEM_BUG_ON(!intel_engine_pm_is_awake(engine));

        p = pulse_create();
        if (!p)
                return -ENOMEM;

        err = i915_active_acquire(&p->active);
        if (err)
                goto out;

        err = i915_active_acquire_preallocate_barrier(&p->active, engine);
        if (err) {
                i915_active_release(&p->active);
                goto out;
        }

        i915_active_acquire_barrier(&p->active);
        i915_active_release(&p->active);

        GEM_BUG_ON(i915_active_is_idle(&p->active));
        GEM_BUG_ON(llist_empty(&engine->barrier_tasks));

        err = fn(engine);
        if (err)
                goto out;

        GEM_BUG_ON(!llist_empty(&engine->barrier_tasks));

        if (engine_sync_barrier(engine)) {
                struct drm_printer m = drm_err_printer(&engine->i915->drm, "pulse");

                drm_printf(&m, "%s: no heartbeat pulse?\n", engine->name);
                intel_engine_dump(engine, &m, "%s", engine->name);

                err = -ETIME;
                goto out;
        }

        GEM_BUG_ON(READ_ONCE(engine->serial) != engine->wakeref_serial);

        pulse_unlock_wait(p); /* synchronize with the retirement callback */

        if (!i915_active_is_idle(&p->active)) {
                struct drm_printer m = drm_err_printer(&engine->i915->drm, "pulse");

                drm_printf(&m, "%s: heartbeat pulse did not flush idle tasks\n",
                           engine->name);
                i915_active_print(&p->active, &m);

                err = -EINVAL;
                goto out;
        }

out:
        pulse_put(p);
        return err;
}

static int live_idle_flush(void *arg)
{
        struct intel_gt *gt = arg;
        struct intel_engine_cs *engine;
        enum intel_engine_id id;
        int err = 0;

        /* Check that we can flush the idle barriers */

        for_each_engine(engine, gt, id) {
                st_engine_heartbeat_disable(engine);
                err = __live_idle_pulse(engine, intel_engine_flush_barriers);
                st_engine_heartbeat_enable(engine);
                if (err)
                        break;
        }

        return err;
}

static int live_idle_pulse(void *arg)
{
        struct intel_gt *gt = arg;
        struct intel_engine_cs *engine;
        enum intel_engine_id id;
        int err = 0;

        /* Check that heartbeat pulses flush the idle barriers */

        for_each_engine(engine, gt, id) {
                st_engine_heartbeat_disable(engine);
                err = __live_idle_pulse(engine, intel_engine_pulse);
                st_engine_heartbeat_enable(engine);
                if (err && err != -ENODEV)
                        break;

                err = 0;
        }

        return err;
}

static int __live_heartbeat_off(struct intel_engine_cs *engine)
{
        int err;

        intel_engine_pm_get(engine);

        engine->serial++;
        flush_delayed_work(&engine->heartbeat.work);
        if (!delayed_work_pending(&engine->heartbeat.work)) {
                pr_err("%s: heartbeat not running\n",
                       engine->name);
                err = -EINVAL;
                goto err_pm;
        }

        err = intel_engine_set_heartbeat(engine, 0);
        if (err)
                goto err_pm;

        engine->serial++;
        flush_delayed_work(&engine->heartbeat.work);
        if (delayed_work_pending(&engine->heartbeat.work)) {
                pr_err("%s: heartbeat still running\n",
                       engine->name);
                err = -EINVAL;
                goto err_beat;
        }

        if (READ_ONCE(engine->heartbeat.systole)) {
                pr_err("%s: heartbeat still allocated\n",
                       engine->name);
                err = -EINVAL;
                goto err_beat;
        }

err_beat:
        reset_heartbeat(engine);
err_pm:
        intel_engine_pm_put(engine);
        return err;
}

static int live_heartbeat_off(void *arg)
{
        struct intel_gt *gt = arg;
        struct intel_engine_cs *engine;
        enum intel_engine_id id;
        int err = 0;

        /* Check that we can turn off heartbeat and not interrupt VIP */
        if (!CONFIG_DRM_I915_HEARTBEAT_INTERVAL)
                return 0;

        for_each_engine(engine, gt, id) {
                if (!intel_engine_has_preemption(engine))
                        continue;

                err = __live_heartbeat_off(engine);
                if (err)
                        break;
        }

        return err;
}

int intel_heartbeat_live_selftests(struct drm_i915_private *i915)
{
        static const struct i915_subtest tests[] = {
                SUBTEST(live_idle_flush),
                SUBTEST(live_idle_pulse),
                SUBTEST(live_heartbeat_off),
        };
        int saved_hangcheck;
        int err;

        if (intel_gt_is_wedged(to_gt(i915)))
                return 0;

        saved_hangcheck = i915->params.enable_hangcheck;
        i915->params.enable_hangcheck = INT_MAX;

        err = intel_gt_live_subtests(tests, to_gt(i915));

        i915->params.enable_hangcheck = saved_hangcheck;
        return err;
}

void st_engine_heartbeat_disable(struct intel_engine_cs *engine)
{
        engine->props.heartbeat_interval_ms = 0;

        intel_engine_pm_get(engine);
        intel_engine_park_heartbeat(engine);
}

void st_engine_heartbeat_enable(struct intel_engine_cs *engine)
{
        intel_engine_pm_put(engine);

        engine->props.heartbeat_interval_ms =
                engine->defaults.heartbeat_interval_ms;
}

void st_engine_heartbeat_disable_no_pm(struct intel_engine_cs *engine)
{
        engine->props.heartbeat_interval_ms = 0;

        /*
         * Park the heartbeat but without holding the PM lock as that
         * makes the engines appear not-idle. Note that if/when unpark
         * is called due to the PM lock being acquired later the
         * heartbeat still won't be enabled because of the above = 0.
         */
        if (intel_engine_pm_get_if_awake(engine)) {
                intel_engine_park_heartbeat(engine);
                intel_engine_pm_put(engine);
        }
}

void st_engine_heartbeat_enable_no_pm(struct intel_engine_cs *engine)
{
        engine->props.heartbeat_interval_ms =
                engine->defaults.heartbeat_interval_ms;
}