|
|
|
|
@@ -3,18 +3,23 @@
|
|
|
|
|
* Copyright © 2023-2024 Intel Corporation
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <linux/nospec.h>
|
|
|
|
|
|
|
|
|
|
#include <drm/drm_managed.h>
|
|
|
|
|
#include <drm/xe_drm.h>
|
|
|
|
|
|
|
|
|
|
#include "regs/xe_gt_regs.h"
|
|
|
|
|
#include "regs/xe_oa_regs.h"
|
|
|
|
|
#include "xe_assert.h"
|
|
|
|
|
#include "xe_device.h"
|
|
|
|
|
#include "xe_exec_queue.h"
|
|
|
|
|
#include "xe_gt.h"
|
|
|
|
|
#include "xe_gt_printk.h"
|
|
|
|
|
#include "xe_macros.h"
|
|
|
|
|
#include "xe_mmio.h"
|
|
|
|
|
#include "xe_oa.h"
|
|
|
|
|
#include "xe_perf.h"
|
|
|
|
|
#include "xe_pm.h"
|
|
|
|
|
|
|
|
|
|
#define XE_OA_UNIT_INVALID U32_MAX
|
|
|
|
|
|
|
|
|
|
@@ -40,6 +45,19 @@ struct xe_oa_config {
|
|
|
|
|
struct rcu_head rcu;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct xe_oa_open_param {
|
|
|
|
|
u32 oa_unit_id;
|
|
|
|
|
bool sample;
|
|
|
|
|
u32 metric_set;
|
|
|
|
|
enum xe_oa_format_name oa_format;
|
|
|
|
|
int period_exponent;
|
|
|
|
|
bool disabled;
|
|
|
|
|
int exec_queue_id;
|
|
|
|
|
int engine_instance;
|
|
|
|
|
struct xe_exec_queue *exec_q;
|
|
|
|
|
struct xe_hw_engine *hwe;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define DRM_FMT(x) DRM_XE_OA_FMT_TYPE_##x
|
|
|
|
|
|
|
|
|
|
static const struct xe_oa_format oa_formats[] = {
|
|
|
|
|
@@ -82,6 +100,352 @@ static void xe_oa_config_put(struct xe_oa_config *oa_config)
|
|
|
|
|
kref_put(&oa_config->ref, xe_oa_config_release);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* xe_oa_timestamp_frequency - Return OA timestamp frequency
|
|
|
|
|
* @gt: @xe_gt
|
|
|
|
|
*
|
|
|
|
|
* OA timestamp frequency = CS timestamp frequency in most platforms. On some
|
|
|
|
|
* platforms OA unit ignores the CTC_SHIFT and the 2 timestamps differ. In such
|
|
|
|
|
* cases, return the adjusted CS timestamp frequency to the user.
|
|
|
|
|
*/
|
|
|
|
|
u32 xe_oa_timestamp_frequency(struct xe_gt *gt)
|
|
|
|
|
{
|
|
|
|
|
u32 reg, shift;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Wa_18013179988:dg2
|
|
|
|
|
* Wa_14015568240:pvc
|
|
|
|
|
* Wa_14015846243:mtl
|
|
|
|
|
*/
|
|
|
|
|
switch (gt_to_xe(gt)->info.platform) {
|
|
|
|
|
case XE_DG2:
|
|
|
|
|
case XE_PVC:
|
|
|
|
|
case XE_METEORLAKE:
|
|
|
|
|
xe_pm_runtime_get(gt_to_xe(gt));
|
|
|
|
|
reg = xe_mmio_read32(gt, RPM_CONFIG0);
|
|
|
|
|
xe_pm_runtime_put(gt_to_xe(gt));
|
|
|
|
|
|
|
|
|
|
shift = REG_FIELD_GET(RPM_CONFIG0_CTC_SHIFT_PARAMETER_MASK, reg);
|
|
|
|
|
return gt->info.reference_clock << (3 - shift);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return gt->info.reference_clock;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static u64 oa_exponent_to_ns(struct xe_gt *gt, int exponent)
|
|
|
|
|
{
|
|
|
|
|
u64 nom = (2ULL << exponent) * NSEC_PER_SEC;
|
|
|
|
|
u32 den = xe_oa_timestamp_frequency(gt);
|
|
|
|
|
|
|
|
|
|
return div_u64(nom + den - 1, den);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool engine_supports_oa_format(const struct xe_hw_engine *hwe, int type)
|
|
|
|
|
{
|
|
|
|
|
switch (hwe->oa_unit->type) {
|
|
|
|
|
case DRM_XE_OA_UNIT_TYPE_OAG:
|
|
|
|
|
return type == DRM_XE_OA_FMT_TYPE_OAG || type == DRM_XE_OA_FMT_TYPE_OAR ||
|
|
|
|
|
type == DRM_XE_OA_FMT_TYPE_OAC || type == DRM_XE_OA_FMT_TYPE_PEC;
|
|
|
|
|
case DRM_XE_OA_UNIT_TYPE_OAM:
|
|
|
|
|
return type == DRM_XE_OA_FMT_TYPE_OAM || type == DRM_XE_OA_FMT_TYPE_OAM_MPEC;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int decode_oa_format(struct xe_oa *oa, u64 fmt, enum xe_oa_format_name *name)
|
|
|
|
|
{
|
|
|
|
|
u32 counter_size = FIELD_GET(DRM_XE_OA_FORMAT_MASK_COUNTER_SIZE, fmt);
|
|
|
|
|
u32 counter_sel = FIELD_GET(DRM_XE_OA_FORMAT_MASK_COUNTER_SEL, fmt);
|
|
|
|
|
u32 bc_report = FIELD_GET(DRM_XE_OA_FORMAT_MASK_BC_REPORT, fmt);
|
|
|
|
|
u32 type = FIELD_GET(DRM_XE_OA_FORMAT_MASK_FMT_TYPE, fmt);
|
|
|
|
|
int idx;
|
|
|
|
|
|
|
|
|
|
for_each_set_bit(idx, oa->format_mask, __XE_OA_FORMAT_MAX) {
|
|
|
|
|
const struct xe_oa_format *f = &oa->oa_formats[idx];
|
|
|
|
|
|
|
|
|
|
if (counter_size == f->counter_size && bc_report == f->bc_report &&
|
|
|
|
|
type == f->type && counter_sel == f->counter_select) {
|
|
|
|
|
*name = idx;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* xe_oa_unit_id - Return OA unit ID for a hardware engine
|
|
|
|
|
* @hwe: @xe_hw_engine
|
|
|
|
|
*
|
|
|
|
|
* Return OA unit ID for a hardware engine when available
|
|
|
|
|
*/
|
|
|
|
|
u16 xe_oa_unit_id(struct xe_hw_engine *hwe)
|
|
|
|
|
{
|
|
|
|
|
return hwe->oa_unit && hwe->oa_unit->num_engines ?
|
|
|
|
|
hwe->oa_unit->oa_unit_id : U16_MAX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_assign_hwe(struct xe_oa *oa, struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
struct xe_gt *gt;
|
|
|
|
|
int i, ret = 0;
|
|
|
|
|
|
|
|
|
|
if (param->exec_q) {
|
|
|
|
|
/* When we have an exec_q, get hwe from the exec_q */
|
|
|
|
|
param->hwe = xe_gt_hw_engine(param->exec_q->gt, param->exec_q->class,
|
|
|
|
|
param->engine_instance, true);
|
|
|
|
|
} else {
|
|
|
|
|
struct xe_hw_engine *hwe;
|
|
|
|
|
enum xe_hw_engine_id id;
|
|
|
|
|
|
|
|
|
|
/* Else just get the first hwe attached to the oa unit */
|
|
|
|
|
for_each_gt(gt, oa->xe, i) {
|
|
|
|
|
for_each_hw_engine(hwe, gt, id) {
|
|
|
|
|
if (xe_oa_unit_id(hwe) == param->oa_unit_id) {
|
|
|
|
|
param->hwe = hwe;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out:
|
|
|
|
|
if (!param->hwe || xe_oa_unit_id(param->hwe) != param->oa_unit_id) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "Unable to find hwe (%d, %d) for OA unit ID %d\n",
|
|
|
|
|
param->exec_q ? param->exec_q->class : -1,
|
|
|
|
|
param->engine_instance, param->oa_unit_id);
|
|
|
|
|
ret = -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_oa_unit_id(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
if (value >= oa->oa_unit_ids) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "OA unit ID out of range %lld\n", value);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
param->oa_unit_id = value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_sample_oa(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
param->sample = value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_metric_set(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
param->metric_set = value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_oa_format(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
int ret = decode_oa_format(oa, value, ¶m->oa_format);
|
|
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "Unsupported OA report format %#llx\n", value);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_oa_exponent(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
#define OA_EXPONENT_MAX 31
|
|
|
|
|
|
|
|
|
|
if (value > OA_EXPONENT_MAX) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "OA timer exponent too high (> %u)\n", OA_EXPONENT_MAX);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
param->period_exponent = value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_disabled(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
param->disabled = value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_exec_queue_id(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
param->exec_queue_id = value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int xe_oa_set_prop_engine_instance(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
param->engine_instance = value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef int (*xe_oa_set_property_fn)(struct xe_oa *oa, u64 value,
|
|
|
|
|
struct xe_oa_open_param *param);
|
|
|
|
|
static const xe_oa_set_property_fn xe_oa_set_property_funcs[] = {
|
|
|
|
|
[DRM_XE_OA_PROPERTY_OA_UNIT_ID] = xe_oa_set_prop_oa_unit_id,
|
|
|
|
|
[DRM_XE_OA_PROPERTY_SAMPLE_OA] = xe_oa_set_prop_sample_oa,
|
|
|
|
|
[DRM_XE_OA_PROPERTY_OA_METRIC_SET] = xe_oa_set_prop_metric_set,
|
|
|
|
|
[DRM_XE_OA_PROPERTY_OA_FORMAT] = xe_oa_set_prop_oa_format,
|
|
|
|
|
[DRM_XE_OA_PROPERTY_OA_PERIOD_EXPONENT] = xe_oa_set_prop_oa_exponent,
|
|
|
|
|
[DRM_XE_OA_PROPERTY_OA_DISABLED] = xe_oa_set_prop_disabled,
|
|
|
|
|
[DRM_XE_OA_PROPERTY_EXEC_QUEUE_ID] = xe_oa_set_prop_exec_queue_id,
|
|
|
|
|
[DRM_XE_OA_PROPERTY_OA_ENGINE_INSTANCE] = xe_oa_set_prop_engine_instance,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int xe_oa_user_ext_set_property(struct xe_oa *oa, u64 extension,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
u64 __user *address = u64_to_user_ptr(extension);
|
|
|
|
|
struct drm_xe_ext_set_property ext;
|
|
|
|
|
int err;
|
|
|
|
|
u32 idx;
|
|
|
|
|
|
|
|
|
|
err = __copy_from_user(&ext, address, sizeof(ext));
|
|
|
|
|
if (XE_IOCTL_DBG(oa->xe, err))
|
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
|
|
if (XE_IOCTL_DBG(oa->xe, ext.property >= ARRAY_SIZE(xe_oa_set_property_funcs)) ||
|
|
|
|
|
XE_IOCTL_DBG(oa->xe, ext.pad))
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
idx = array_index_nospec(ext.property, ARRAY_SIZE(xe_oa_set_property_funcs));
|
|
|
|
|
return xe_oa_set_property_funcs[idx](oa, ext.value, param);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef int (*xe_oa_user_extension_fn)(struct xe_oa *oa, u64 extension,
|
|
|
|
|
struct xe_oa_open_param *param);
|
|
|
|
|
static const xe_oa_user_extension_fn xe_oa_user_extension_funcs[] = {
|
|
|
|
|
[DRM_XE_OA_EXTENSION_SET_PROPERTY] = xe_oa_user_ext_set_property,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int xe_oa_user_extensions(struct xe_oa *oa, u64 extension, int ext_number,
|
|
|
|
|
struct xe_oa_open_param *param)
|
|
|
|
|
{
|
|
|
|
|
u64 __user *address = u64_to_user_ptr(extension);
|
|
|
|
|
struct drm_xe_user_extension ext;
|
|
|
|
|
int err;
|
|
|
|
|
u32 idx;
|
|
|
|
|
|
|
|
|
|
if (XE_IOCTL_DBG(oa->xe, ext_number >= DRM_XE_OA_PROPERTY_MAX))
|
|
|
|
|
return -E2BIG;
|
|
|
|
|
|
|
|
|
|
err = __copy_from_user(&ext, address, sizeof(ext));
|
|
|
|
|
if (XE_IOCTL_DBG(oa->xe, err))
|
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
|
|
if (XE_IOCTL_DBG(oa->xe, ext.pad) ||
|
|
|
|
|
XE_IOCTL_DBG(oa->xe, ext.name >= ARRAY_SIZE(xe_oa_user_extension_funcs)))
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
idx = array_index_nospec(ext.name, ARRAY_SIZE(xe_oa_user_extension_funcs));
|
|
|
|
|
err = xe_oa_user_extension_funcs[idx](oa, extension, param);
|
|
|
|
|
if (XE_IOCTL_DBG(oa->xe, err))
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
if (ext.next_extension)
|
|
|
|
|
return xe_oa_user_extensions(oa, ext.next_extension, ++ext_number, param);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* xe_oa_stream_open_ioctl - Opens an OA stream
|
|
|
|
|
* @dev: @drm_device
|
|
|
|
|
* @data: pointer to struct @drm_xe_oa_config
|
|
|
|
|
* @file: @drm_file
|
|
|
|
|
*
|
|
|
|
|
* The functions opens an OA stream. An OA stream, opened with specified
|
|
|
|
|
* properties, enables perf counter samples to be collected, either
|
|
|
|
|
* periodically (time based sampling), or on request (using perf queries)
|
|
|
|
|
*/
|
|
|
|
|
int xe_oa_stream_open_ioctl(struct drm_device *dev, u64 data, struct drm_file *file)
|
|
|
|
|
{
|
|
|
|
|
struct xe_oa *oa = &to_xe_device(dev)->oa;
|
|
|
|
|
struct xe_file *xef = to_xe_file(file);
|
|
|
|
|
struct xe_oa_open_param param = {};
|
|
|
|
|
const struct xe_oa_format *f;
|
|
|
|
|
bool privileged_op = true;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (!oa->xe) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "xe oa interface not available for this system\n");
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = xe_oa_user_extensions(oa, data, 0, ¶m);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
if (param.exec_queue_id > 0) {
|
|
|
|
|
param.exec_q = xe_exec_queue_lookup(xef, param.exec_queue_id);
|
|
|
|
|
if (XE_IOCTL_DBG(oa->xe, !param.exec_q))
|
|
|
|
|
return -ENOENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Query based sampling (using MI_REPORT_PERF_COUNT) with OAR/OAC,
|
|
|
|
|
* without global stream access, can be an unprivileged operation
|
|
|
|
|
*/
|
|
|
|
|
if (param.exec_q && !param.sample)
|
|
|
|
|
privileged_op = false;
|
|
|
|
|
|
|
|
|
|
if (privileged_op && xe_perf_stream_paranoid && !perfmon_capable()) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "Insufficient privileges to open xe perf stream\n");
|
|
|
|
|
ret = -EACCES;
|
|
|
|
|
goto err_exec_q;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!param.exec_q && !param.sample) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "Only OA report sampling supported\n");
|
|
|
|
|
ret = -EINVAL;
|
|
|
|
|
goto err_exec_q;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = xe_oa_assign_hwe(oa, ¶m);
|
|
|
|
|
if (ret)
|
|
|
|
|
goto err_exec_q;
|
|
|
|
|
|
|
|
|
|
f = &oa->oa_formats[param.oa_format];
|
|
|
|
|
if (!param.oa_format || !f->size ||
|
|
|
|
|
!engine_supports_oa_format(param.hwe, f->type)) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "Invalid OA format %d type %d size %d for class %d\n",
|
|
|
|
|
param.oa_format, f->type, f->size, param.hwe->class);
|
|
|
|
|
ret = -EINVAL;
|
|
|
|
|
goto err_exec_q;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (param.period_exponent > 0) {
|
|
|
|
|
u64 oa_period, oa_freq_hz;
|
|
|
|
|
|
|
|
|
|
/* Requesting samples from OAG buffer is a privileged operation */
|
|
|
|
|
if (!param.sample) {
|
|
|
|
|
drm_dbg(&oa->xe->drm, "OA_EXPONENT specified without SAMPLE_OA\n");
|
|
|
|
|
ret = -EINVAL;
|
|
|
|
|
goto err_exec_q;
|
|
|
|
|
}
|
|
|
|
|
oa_period = oa_exponent_to_ns(param.hwe->gt, param.period_exponent);
|
|
|
|
|
oa_freq_hz = div64_u64(NSEC_PER_SEC, oa_period);
|
|
|
|
|
drm_dbg(&oa->xe->drm, "Using periodic sampling freq %lld Hz\n", oa_freq_hz);
|
|
|
|
|
}
|
|
|
|
|
err_exec_q:
|
|
|
|
|
if (ret < 0 && param.exec_q)
|
|
|
|
|
xe_exec_queue_put(param.exec_q);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool xe_oa_is_valid_flex_addr(struct xe_oa *oa, u32 addr)
|
|
|
|
|
{
|
|
|
|
|
static const struct xe_reg flex_eu_regs[] = {
|
|
|
|
|
|