linux/drivers/gpu/drm/i915/gt/sysfs_engines.c
Chris Wilson 6971e07b6b drm/i915/gt: Cleanup kasan warning for on-stack (unsigned long) casting
Kasan is gving a warning for passing a u32 parameter into find_first_bit
(casting to a unsigned long *, with appropriate length restrictions):

[   44.678262] BUG: KASAN: stack-out-of-bounds in find_first_bit+0x2e/0x50
[   44.678295] Read of size 8 at addr ffff888233f4fc30 by task core_hotunplug/474
[   44.678326]
[   44.678358] CPU: 0 PID: 474 Comm: core_hotunplug Not tainted 5.9.0+ #608
[   44.678465] Hardware name: BESSTAR (HK) LIMITED GN41/Default string, BIOS BLT-BI-MINIPC-F4G-EX3R110-GA65A-101-D 10/12/2018
[   44.678500] Call Trace:
[   44.678534]  dump_stack+0x84/0xba
[   44.678569]  print_address_description.constprop.0+0x21/0x220
[   44.678605]  ? kmsg_dump_rewind_nolock+0x5f/0x5f
[   44.678638]  ? _raw_spin_lock_irqsave+0x6d/0xb0
[   44.678669]  ? _raw_write_lock_irqsave+0xb0/0xb0
[   44.678702]  ? set_task_cpu+0x1e0/0x1e0
[   44.678733]  ? find_first_bit+0x2e/0x50
[   44.678763]  kasan_report.cold+0x20/0x42
[   44.678794]  ? find_first_bit+0x2e/0x50
[   44.678825]  __asan_load8+0x69/0x90
[   44.678856]  find_first_bit+0x2e/0x50
[   44.679027]  __caps_show.isra.0+0x9e/0x1f0 [i915]

Since we are only using the shorter type for our own convenience,
accommodate kasan and use unsigned long.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Reviewed-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20201013110845.16127-1-chris@chris-wilson.co.uk
2020-10-16 11:08:44 +01:00

536 lines
14 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright © 2019 Intel Corporation
*/
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include "i915_drv.h"
#include "intel_engine.h"
#include "intel_engine_heartbeat.h"
#include "sysfs_engines.h"
struct kobj_engine {
struct kobject base;
struct intel_engine_cs *engine;
};
static struct intel_engine_cs *kobj_to_engine(struct kobject *kobj)
{
return container_of(kobj, struct kobj_engine, base)->engine;
}
static ssize_t
name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", kobj_to_engine(kobj)->name);
}
static struct kobj_attribute name_attr =
__ATTR(name, 0444, name_show, NULL);
static ssize_t
class_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", kobj_to_engine(kobj)->uabi_class);
}
static struct kobj_attribute class_attr =
__ATTR(class, 0444, class_show, NULL);
static ssize_t
inst_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", kobj_to_engine(kobj)->uabi_instance);
}
static struct kobj_attribute inst_attr =
__ATTR(instance, 0444, inst_show, NULL);
static ssize_t
mmio_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "0x%x\n", kobj_to_engine(kobj)->mmio_base);
}
static struct kobj_attribute mmio_attr =
__ATTR(mmio_base, 0444, mmio_show, NULL);
static const char * const vcs_caps[] = {
[ilog2(I915_VIDEO_CLASS_CAPABILITY_HEVC)] = "hevc",
[ilog2(I915_VIDEO_AND_ENHANCE_CLASS_CAPABILITY_SFC)] = "sfc",
};
static const char * const vecs_caps[] = {
[ilog2(I915_VIDEO_AND_ENHANCE_CLASS_CAPABILITY_SFC)] = "sfc",
};
static ssize_t repr_trim(char *buf, ssize_t len)
{
/* Trim off the trailing space and replace with a newline */
if (len > PAGE_SIZE)
len = PAGE_SIZE;
if (len > 0)
buf[len - 1] = '\n';
return len;
}
static ssize_t
__caps_show(struct intel_engine_cs *engine,
unsigned long caps, char *buf, bool show_unknown)
{
const char * const *repr;
int count, n;
ssize_t len;
switch (engine->class) {
case VIDEO_DECODE_CLASS:
repr = vcs_caps;
count = ARRAY_SIZE(vcs_caps);
break;
case VIDEO_ENHANCEMENT_CLASS:
repr = vecs_caps;
count = ARRAY_SIZE(vecs_caps);
break;
default:
repr = NULL;
count = 0;
break;
}
GEM_BUG_ON(count > BITS_PER_LONG);
len = 0;
for_each_set_bit(n, &caps, show_unknown ? BITS_PER_LONG : count) {
if (n >= count || !repr[n]) {
if (GEM_WARN_ON(show_unknown))
len += snprintf(buf + len, PAGE_SIZE - len,
"[%x] ", n);
} else {
len += snprintf(buf + len, PAGE_SIZE - len,
"%s ", repr[n]);
}
if (GEM_WARN_ON(len >= PAGE_SIZE))
break;
}
return repr_trim(buf, len);
}
static ssize_t
caps_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return __caps_show(engine, engine->uabi_capabilities, buf, true);
}
static struct kobj_attribute caps_attr =
__ATTR(capabilities, 0444, caps_show, NULL);
static ssize_t
all_caps_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return __caps_show(kobj_to_engine(kobj), -1, buf, false);
}
static struct kobj_attribute all_caps_attr =
__ATTR(known_capabilities, 0444, all_caps_show, NULL);
static ssize_t
max_spin_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
unsigned long long duration;
int err;
/*
* When waiting for a request, if is it currently being executed
* on the GPU, we busywait for a short while before sleeping. The
* premise is that most requests are short, and if it is already
* executing then there is a good chance that it will complete
* before we can setup the interrupt handler and go to sleep.
* We try to offset the cost of going to sleep, by first spinning
* on the request -- if it completed in less time than it would take
* to go sleep, process the interrupt and return back to the client,
* then we have saved the client some latency, albeit at the cost
* of spinning on an expensive CPU core.
*
* While we try to avoid waiting at all for a request that is unlikely
* to complete, deciding how long it is worth spinning is for is an
* arbitrary decision: trading off power vs latency.
*/
err = kstrtoull(buf, 0, &duration);
if (err)
return err;
if (duration > jiffies_to_nsecs(2))
return -EINVAL;
WRITE_ONCE(engine->props.max_busywait_duration_ns, duration);
return count;
}
static ssize_t
max_spin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->props.max_busywait_duration_ns);
}
static struct kobj_attribute max_spin_attr =
__ATTR(max_busywait_duration_ns, 0644, max_spin_show, max_spin_store);
static ssize_t
max_spin_default(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->defaults.max_busywait_duration_ns);
}
static struct kobj_attribute max_spin_def =
__ATTR(max_busywait_duration_ns, 0444, max_spin_default, NULL);
static ssize_t
timeslice_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
unsigned long long duration;
int err;
/*
* Execlists uses a scheduling quantum (a timeslice) to alternate
* execution between ready-to-run contexts of equal priority. This
* ensures that all users (though only if they of equal importance)
* have the opportunity to run and prevents livelocks where contexts
* may have implicit ordering due to userspace semaphores.
*/
err = kstrtoull(buf, 0, &duration);
if (err)
return err;
if (duration > jiffies_to_msecs(MAX_SCHEDULE_TIMEOUT))
return -EINVAL;
WRITE_ONCE(engine->props.timeslice_duration_ms, duration);
if (execlists_active(&engine->execlists))
set_timer_ms(&engine->execlists.timer, duration);
return count;
}
static ssize_t
timeslice_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->props.timeslice_duration_ms);
}
static struct kobj_attribute timeslice_duration_attr =
__ATTR(timeslice_duration_ms, 0644, timeslice_show, timeslice_store);
static ssize_t
timeslice_default(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->defaults.timeslice_duration_ms);
}
static struct kobj_attribute timeslice_duration_def =
__ATTR(timeslice_duration_ms, 0444, timeslice_default, NULL);
static ssize_t
stop_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
unsigned long long duration;
int err;
/*
* When we allow ourselves to sleep before a GPU reset after disabling
* submission, even for a few milliseconds, gives an innocent context
* the opportunity to clear the GPU before the reset occurs. However,
* how long to sleep depends on the typical non-preemptible duration
* (a similar problem to determining the ideal preempt-reset timeout
* or even the heartbeat interval).
*/
err = kstrtoull(buf, 0, &duration);
if (err)
return err;
if (duration > jiffies_to_msecs(MAX_SCHEDULE_TIMEOUT))
return -EINVAL;
WRITE_ONCE(engine->props.stop_timeout_ms, duration);
return count;
}
static ssize_t
stop_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->props.stop_timeout_ms);
}
static struct kobj_attribute stop_timeout_attr =
__ATTR(stop_timeout_ms, 0644, stop_show, stop_store);
static ssize_t
stop_default(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->defaults.stop_timeout_ms);
}
static struct kobj_attribute stop_timeout_def =
__ATTR(stop_timeout_ms, 0444, stop_default, NULL);
static ssize_t
preempt_timeout_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
unsigned long long timeout;
int err;
/*
* After initialising a preemption request, we give the current
* resident a small amount of time to vacate the GPU. The preemption
* request is for a higher priority context and should be immediate to
* maintain high quality of service (and avoid priority inversion).
* However, the preemption granularity of the GPU can be quite coarse
* and so we need a compromise.
*/
err = kstrtoull(buf, 0, &timeout);
if (err)
return err;
if (timeout > jiffies_to_msecs(MAX_SCHEDULE_TIMEOUT))
return -EINVAL;
WRITE_ONCE(engine->props.preempt_timeout_ms, timeout);
if (READ_ONCE(engine->execlists.pending[0]))
set_timer_ms(&engine->execlists.preempt, timeout);
return count;
}
static ssize_t
preempt_timeout_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->props.preempt_timeout_ms);
}
static struct kobj_attribute preempt_timeout_attr =
__ATTR(preempt_timeout_ms, 0644, preempt_timeout_show, preempt_timeout_store);
static ssize_t
preempt_timeout_default(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->defaults.preempt_timeout_ms);
}
static struct kobj_attribute preempt_timeout_def =
__ATTR(preempt_timeout_ms, 0444, preempt_timeout_default, NULL);
static ssize_t
heartbeat_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
unsigned long long delay;
int err;
/*
* We monitor the health of the system via periodic heartbeat pulses.
* The pulses also provide the opportunity to perform garbage
* collection. However, we interpret an incomplete pulse (a missed
* heartbeat) as an indication that the system is no longer responsive,
* i.e. hung, and perform an engine or full GPU reset. Given that the
* preemption granularity can be very coarse on a system, the optimal
* value for any workload is unknowable!
*/
err = kstrtoull(buf, 0, &delay);
if (err)
return err;
if (delay >= jiffies_to_msecs(MAX_SCHEDULE_TIMEOUT))
return -EINVAL;
err = intel_engine_set_heartbeat(engine, delay);
if (err)
return err;
return count;
}
static ssize_t
heartbeat_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->props.heartbeat_interval_ms);
}
static struct kobj_attribute heartbeat_interval_attr =
__ATTR(heartbeat_interval_ms, 0644, heartbeat_show, heartbeat_store);
static ssize_t
heartbeat_default(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct intel_engine_cs *engine = kobj_to_engine(kobj);
return sprintf(buf, "%lu\n", engine->defaults.heartbeat_interval_ms);
}
static struct kobj_attribute heartbeat_interval_def =
__ATTR(heartbeat_interval_ms, 0444, heartbeat_default, NULL);
static void kobj_engine_release(struct kobject *kobj)
{
kfree(kobj);
}
static struct kobj_type kobj_engine_type = {
.release = kobj_engine_release,
.sysfs_ops = &kobj_sysfs_ops
};
static struct kobject *
kobj_engine(struct kobject *dir, struct intel_engine_cs *engine)
{
struct kobj_engine *ke;
ke = kzalloc(sizeof(*ke), GFP_KERNEL);
if (!ke)
return NULL;
kobject_init(&ke->base, &kobj_engine_type);
ke->engine = engine;
if (kobject_add(&ke->base, dir, "%s", engine->name)) {
kobject_put(&ke->base);
return NULL;
}
/* xfer ownership to sysfs tree */
return &ke->base;
}
static void add_defaults(struct kobj_engine *parent)
{
static const struct attribute *files[] = {
&max_spin_def.attr,
&stop_timeout_def.attr,
#if CONFIG_DRM_I915_HEARTBEAT_INTERVAL
&heartbeat_interval_def.attr,
#endif
NULL
};
struct kobj_engine *ke;
ke = kzalloc(sizeof(*ke), GFP_KERNEL);
if (!ke)
return;
kobject_init(&ke->base, &kobj_engine_type);
ke->engine = parent->engine;
if (kobject_add(&ke->base, &parent->base, "%s", ".defaults")) {
kobject_put(&ke->base);
return;
}
if (sysfs_create_files(&ke->base, files))
return;
if (intel_engine_has_timeslices(ke->engine) &&
sysfs_create_file(&ke->base, &timeslice_duration_def.attr))
return;
if (intel_engine_has_preempt_reset(ke->engine) &&
sysfs_create_file(&ke->base, &preempt_timeout_def.attr))
return;
}
void intel_engines_add_sysfs(struct drm_i915_private *i915)
{
static const struct attribute *files[] = {
&name_attr.attr,
&class_attr.attr,
&inst_attr.attr,
&mmio_attr.attr,
&caps_attr.attr,
&all_caps_attr.attr,
&max_spin_attr.attr,
&stop_timeout_attr.attr,
#if CONFIG_DRM_I915_HEARTBEAT_INTERVAL
&heartbeat_interval_attr.attr,
#endif
NULL
};
struct device *kdev = i915->drm.primary->kdev;
struct intel_engine_cs *engine;
struct kobject *dir;
dir = kobject_create_and_add("engine", &kdev->kobj);
if (!dir)
return;
for_each_uabi_engine(engine, i915) {
struct kobject *kobj;
kobj = kobj_engine(dir, engine);
if (!kobj)
goto err_engine;
if (sysfs_create_files(kobj, files))
goto err_object;
if (intel_engine_has_timeslices(engine) &&
sysfs_create_file(kobj, &timeslice_duration_attr.attr))
goto err_engine;
if (intel_engine_has_preempt_reset(engine) &&
sysfs_create_file(kobj, &preempt_timeout_attr.attr))
goto err_engine;
add_defaults(container_of(kobj, struct kobj_engine, base));
if (0) {
err_object:
kobject_put(kobj);
err_engine:
dev_err(kdev, "Failed to add sysfs engine '%s'\n",
engine->name);
break;
}
}
}