mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-01-23 15:12:55 +01:00
Currently this is hidden behind perfmon_capable() since this is technically an info leak, given that this is a system wide metric. However the granularity reported here is always PAGE_SIZE aligned, which matches what the core kernel is already willing to expose to userspace if querying how many free RAM pages there are on the system, and that doesn't need any special privileges. In addition other drm drivers seem happy to expose this. The motivation here if with oneAPI where they want to use the system wide 'used' reporting here, so not the per-client fdinfo stats. This has also come up with some perf overlay applications wanting this information. Fixes:1105ac15d2("drm/xe/uapi: restrict system wide accounting") Signed-off-by: Matthew Auld <matthew.auld@intel.com> Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com> Cc: Joshua Santosh <joshua.santosh.ranjan@intel.com> Cc: José Roberto de Souza <jose.souza@intel.com> Cc: Matthew Brost <matthew.brost@intel.com> Cc: Rodrigo Vivi <rodrigo.vivi@intel.com> Cc: <stable@vger.kernel.org> # v6.8+ Acked-by: Rodrigo Vivi <rodrigo.vivi@intel.com> Reviewed-by: Lucas De Marchi <lucas.demarchi@intel.com> Link: https://lore.kernel.org/r/20250919122052.420979-2-matthew.auld@intel.com (cherry picked from commit4d0b035fd6) Signed-off-by: Lucas De Marchi <lucas.demarchi@intel.com>
813 lines
20 KiB
C
813 lines
20 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2022 Intel Corporation
|
|
*/
|
|
|
|
#include "xe_query.h"
|
|
|
|
#include <linux/nospec.h>
|
|
#include <linux/sched/clock.h>
|
|
|
|
#include <drm/ttm/ttm_placement.h>
|
|
#include <generated/xe_wa_oob.h>
|
|
#include <uapi/drm/xe_drm.h>
|
|
|
|
#include "regs/xe_engine_regs.h"
|
|
#include "regs/xe_gt_regs.h"
|
|
#include "xe_bo.h"
|
|
#include "xe_device.h"
|
|
#include "xe_eu_stall.h"
|
|
#include "xe_exec_queue.h"
|
|
#include "xe_force_wake.h"
|
|
#include "xe_ggtt.h"
|
|
#include "xe_gt.h"
|
|
#include "xe_gt_topology.h"
|
|
#include "xe_guc_hwconfig.h"
|
|
#include "xe_macros.h"
|
|
#include "xe_mmio.h"
|
|
#include "xe_oa.h"
|
|
#include "xe_pxp.h"
|
|
#include "xe_ttm_vram_mgr.h"
|
|
#include "xe_vram_types.h"
|
|
#include "xe_wa.h"
|
|
|
|
static const u16 xe_to_user_engine_class[] = {
|
|
[XE_ENGINE_CLASS_RENDER] = DRM_XE_ENGINE_CLASS_RENDER,
|
|
[XE_ENGINE_CLASS_COPY] = DRM_XE_ENGINE_CLASS_COPY,
|
|
[XE_ENGINE_CLASS_VIDEO_DECODE] = DRM_XE_ENGINE_CLASS_VIDEO_DECODE,
|
|
[XE_ENGINE_CLASS_VIDEO_ENHANCE] = DRM_XE_ENGINE_CLASS_VIDEO_ENHANCE,
|
|
[XE_ENGINE_CLASS_COMPUTE] = DRM_XE_ENGINE_CLASS_COMPUTE,
|
|
};
|
|
|
|
static const enum xe_engine_class user_to_xe_engine_class[] = {
|
|
[DRM_XE_ENGINE_CLASS_RENDER] = XE_ENGINE_CLASS_RENDER,
|
|
[DRM_XE_ENGINE_CLASS_COPY] = XE_ENGINE_CLASS_COPY,
|
|
[DRM_XE_ENGINE_CLASS_VIDEO_DECODE] = XE_ENGINE_CLASS_VIDEO_DECODE,
|
|
[DRM_XE_ENGINE_CLASS_VIDEO_ENHANCE] = XE_ENGINE_CLASS_VIDEO_ENHANCE,
|
|
[DRM_XE_ENGINE_CLASS_COMPUTE] = XE_ENGINE_CLASS_COMPUTE,
|
|
};
|
|
|
|
static size_t calc_hw_engine_info_size(struct xe_device *xe)
|
|
{
|
|
struct xe_hw_engine *hwe;
|
|
enum xe_hw_engine_id id;
|
|
struct xe_gt *gt;
|
|
u8 gt_id;
|
|
int i = 0;
|
|
|
|
for_each_gt(gt, xe, gt_id)
|
|
for_each_hw_engine(hwe, gt, id) {
|
|
if (xe_hw_engine_is_reserved(hwe))
|
|
continue;
|
|
i++;
|
|
}
|
|
|
|
return sizeof(struct drm_xe_query_engines) +
|
|
i * sizeof(struct drm_xe_engine);
|
|
}
|
|
|
|
typedef u64 (*__ktime_func_t)(void);
|
|
static __ktime_func_t __clock_id_to_func(clockid_t clk_id)
|
|
{
|
|
/*
|
|
* Use logic same as the perf subsystem to allow user to select the
|
|
* reference clock id to be used for timestamps.
|
|
*/
|
|
switch (clk_id) {
|
|
case CLOCK_MONOTONIC:
|
|
return &ktime_get_ns;
|
|
case CLOCK_MONOTONIC_RAW:
|
|
return &ktime_get_raw_ns;
|
|
case CLOCK_REALTIME:
|
|
return &ktime_get_real_ns;
|
|
case CLOCK_BOOTTIME:
|
|
return &ktime_get_boottime_ns;
|
|
case CLOCK_TAI:
|
|
return &ktime_get_clocktai_ns;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
hwe_read_timestamp(struct xe_hw_engine *hwe, u64 *engine_ts, u64 *cpu_ts,
|
|
u64 *cpu_delta, __ktime_func_t cpu_clock)
|
|
{
|
|
struct xe_mmio *mmio = &hwe->gt->mmio;
|
|
u32 upper, lower, old_upper, loop = 0;
|
|
struct xe_reg upper_reg = RING_TIMESTAMP_UDW(hwe->mmio_base),
|
|
lower_reg = RING_TIMESTAMP(hwe->mmio_base);
|
|
|
|
upper = xe_mmio_read32(mmio, upper_reg);
|
|
do {
|
|
*cpu_delta = local_clock();
|
|
*cpu_ts = cpu_clock();
|
|
lower = xe_mmio_read32(mmio, lower_reg);
|
|
*cpu_delta = local_clock() - *cpu_delta;
|
|
old_upper = upper;
|
|
upper = xe_mmio_read32(mmio, upper_reg);
|
|
} while (upper != old_upper && loop++ < 2);
|
|
|
|
*engine_ts = (u64)upper << 32 | lower;
|
|
}
|
|
|
|
static int
|
|
query_engine_cycles(struct xe_device *xe,
|
|
struct drm_xe_device_query *query)
|
|
{
|
|
struct drm_xe_query_engine_cycles __user *query_ptr;
|
|
struct drm_xe_engine_class_instance *eci;
|
|
struct drm_xe_query_engine_cycles resp;
|
|
size_t size = sizeof(resp);
|
|
__ktime_func_t cpu_clock;
|
|
struct xe_hw_engine *hwe;
|
|
struct xe_gt *gt;
|
|
unsigned int fw_ref;
|
|
|
|
if (IS_SRIOV_VF(xe))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
query_ptr = u64_to_user_ptr(query->data);
|
|
if (copy_from_user(&resp, query_ptr, size))
|
|
return -EFAULT;
|
|
|
|
cpu_clock = __clock_id_to_func(resp.clockid);
|
|
if (!cpu_clock)
|
|
return -EINVAL;
|
|
|
|
eci = &resp.eci;
|
|
if (eci->gt_id >= xe->info.max_gt_per_tile)
|
|
return -EINVAL;
|
|
|
|
gt = xe_device_get_gt(xe, eci->gt_id);
|
|
if (!gt)
|
|
return -EINVAL;
|
|
|
|
if (eci->engine_class >= ARRAY_SIZE(user_to_xe_engine_class))
|
|
return -EINVAL;
|
|
|
|
hwe = xe_gt_hw_engine(gt, user_to_xe_engine_class[eci->engine_class],
|
|
eci->engine_instance, true);
|
|
if (!hwe)
|
|
return -EINVAL;
|
|
|
|
fw_ref = xe_force_wake_get(gt_to_fw(gt), XE_FORCEWAKE_ALL);
|
|
if (!xe_force_wake_ref_has_domain(fw_ref, XE_FORCEWAKE_ALL)) {
|
|
xe_force_wake_put(gt_to_fw(gt), fw_ref);
|
|
return -EIO;
|
|
}
|
|
|
|
hwe_read_timestamp(hwe, &resp.engine_cycles, &resp.cpu_timestamp,
|
|
&resp.cpu_delta, cpu_clock);
|
|
|
|
xe_force_wake_put(gt_to_fw(gt), fw_ref);
|
|
|
|
if (GRAPHICS_VER(xe) >= 20)
|
|
resp.width = 64;
|
|
else
|
|
resp.width = 36;
|
|
|
|
/* Only write to the output fields of user query */
|
|
if (put_user(resp.cpu_timestamp, &query_ptr->cpu_timestamp) ||
|
|
put_user(resp.cpu_delta, &query_ptr->cpu_delta) ||
|
|
put_user(resp.engine_cycles, &query_ptr->engine_cycles) ||
|
|
put_user(resp.width, &query_ptr->width))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int query_engines(struct xe_device *xe,
|
|
struct drm_xe_device_query *query)
|
|
{
|
|
size_t size = calc_hw_engine_info_size(xe);
|
|
struct drm_xe_query_engines __user *query_ptr =
|
|
u64_to_user_ptr(query->data);
|
|
struct drm_xe_query_engines *engines;
|
|
struct xe_hw_engine *hwe;
|
|
enum xe_hw_engine_id id;
|
|
struct xe_gt *gt;
|
|
u8 gt_id;
|
|
int i = 0;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
engines = kzalloc(size, GFP_KERNEL);
|
|
if (!engines)
|
|
return -ENOMEM;
|
|
|
|
for_each_gt(gt, xe, gt_id)
|
|
for_each_hw_engine(hwe, gt, id) {
|
|
if (xe_hw_engine_is_reserved(hwe))
|
|
continue;
|
|
|
|
engines->engines[i].instance.engine_class =
|
|
xe_to_user_engine_class[hwe->class];
|
|
engines->engines[i].instance.engine_instance =
|
|
hwe->logical_instance;
|
|
engines->engines[i].instance.gt_id = gt->info.id;
|
|
|
|
i++;
|
|
}
|
|
|
|
engines->num_engines = i;
|
|
|
|
if (copy_to_user(query_ptr, engines, size)) {
|
|
kfree(engines);
|
|
return -EFAULT;
|
|
}
|
|
kfree(engines);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t calc_mem_regions_size(struct xe_device *xe)
|
|
{
|
|
u32 num_managers = 1;
|
|
int i;
|
|
|
|
for (i = XE_PL_VRAM0; i <= XE_PL_VRAM1; ++i)
|
|
if (ttm_manager_type(&xe->ttm, i))
|
|
num_managers++;
|
|
|
|
return offsetof(struct drm_xe_query_mem_regions, mem_regions[num_managers]);
|
|
}
|
|
|
|
static int query_mem_regions(struct xe_device *xe,
|
|
struct drm_xe_device_query *query)
|
|
{
|
|
size_t size = calc_mem_regions_size(xe);
|
|
struct drm_xe_query_mem_regions *mem_regions;
|
|
struct drm_xe_query_mem_regions __user *query_ptr =
|
|
u64_to_user_ptr(query->data);
|
|
struct ttm_resource_manager *man;
|
|
int ret, i;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
mem_regions = kzalloc(size, GFP_KERNEL);
|
|
if (XE_IOCTL_DBG(xe, !mem_regions))
|
|
return -ENOMEM;
|
|
|
|
man = ttm_manager_type(&xe->ttm, XE_PL_TT);
|
|
mem_regions->mem_regions[0].mem_class = DRM_XE_MEM_REGION_CLASS_SYSMEM;
|
|
/*
|
|
* The instance needs to be a unique number that represents the index
|
|
* in the placement mask used at xe_gem_create_ioctl() for the
|
|
* xe_bo_create() placement.
|
|
*/
|
|
mem_regions->mem_regions[0].instance = 0;
|
|
mem_regions->mem_regions[0].min_page_size = PAGE_SIZE;
|
|
mem_regions->mem_regions[0].total_size = man->size << PAGE_SHIFT;
|
|
mem_regions->mem_regions[0].used = ttm_resource_manager_usage(man);
|
|
mem_regions->num_mem_regions = 1;
|
|
|
|
for (i = XE_PL_VRAM0; i <= XE_PL_VRAM1; ++i) {
|
|
man = ttm_manager_type(&xe->ttm, i);
|
|
if (man) {
|
|
mem_regions->mem_regions[mem_regions->num_mem_regions].mem_class =
|
|
DRM_XE_MEM_REGION_CLASS_VRAM;
|
|
mem_regions->mem_regions[mem_regions->num_mem_regions].instance =
|
|
mem_regions->num_mem_regions;
|
|
mem_regions->mem_regions[mem_regions->num_mem_regions].min_page_size =
|
|
xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K ?
|
|
SZ_64K : PAGE_SIZE;
|
|
mem_regions->mem_regions[mem_regions->num_mem_regions].total_size =
|
|
man->size;
|
|
|
|
xe_ttm_vram_get_used(man,
|
|
&mem_regions->mem_regions
|
|
[mem_regions->num_mem_regions].used,
|
|
&mem_regions->mem_regions
|
|
[mem_regions->num_mem_regions].cpu_visible_used);
|
|
|
|
mem_regions->mem_regions[mem_regions->num_mem_regions].cpu_visible_size =
|
|
xe_ttm_vram_get_cpu_visible_size(man);
|
|
mem_regions->num_mem_regions++;
|
|
}
|
|
}
|
|
|
|
if (!copy_to_user(query_ptr, mem_regions, size))
|
|
ret = 0;
|
|
else
|
|
ret = -ENOSPC;
|
|
|
|
kfree(mem_regions);
|
|
return ret;
|
|
}
|
|
|
|
static int query_config(struct xe_device *xe, struct drm_xe_device_query *query)
|
|
{
|
|
const u32 num_params = DRM_XE_QUERY_CONFIG_MAX_EXEC_QUEUE_PRIORITY + 1;
|
|
size_t size =
|
|
sizeof(struct drm_xe_query_config) + num_params * sizeof(u64);
|
|
struct drm_xe_query_config __user *query_ptr =
|
|
u64_to_user_ptr(query->data);
|
|
struct drm_xe_query_config *config;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
config = kzalloc(size, GFP_KERNEL);
|
|
if (!config)
|
|
return -ENOMEM;
|
|
|
|
config->num_params = num_params;
|
|
config->info[DRM_XE_QUERY_CONFIG_REV_AND_DEVICE_ID] =
|
|
xe->info.devid | (xe->info.revid << 16);
|
|
if (xe->mem.vram)
|
|
config->info[DRM_XE_QUERY_CONFIG_FLAGS] |=
|
|
DRM_XE_QUERY_CONFIG_FLAG_HAS_VRAM;
|
|
if (xe->info.has_usm && IS_ENABLED(CONFIG_DRM_XE_GPUSVM))
|
|
config->info[DRM_XE_QUERY_CONFIG_FLAGS] |=
|
|
DRM_XE_QUERY_CONFIG_FLAG_HAS_CPU_ADDR_MIRROR;
|
|
config->info[DRM_XE_QUERY_CONFIG_FLAGS] |=
|
|
DRM_XE_QUERY_CONFIG_FLAG_HAS_LOW_LATENCY;
|
|
config->info[DRM_XE_QUERY_CONFIG_MIN_ALIGNMENT] =
|
|
xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K ? SZ_64K : SZ_4K;
|
|
config->info[DRM_XE_QUERY_CONFIG_VA_BITS] = xe->info.va_bits;
|
|
config->info[DRM_XE_QUERY_CONFIG_MAX_EXEC_QUEUE_PRIORITY] =
|
|
xe_exec_queue_device_get_max_priority(xe);
|
|
|
|
if (copy_to_user(query_ptr, config, size)) {
|
|
kfree(config);
|
|
return -EFAULT;
|
|
}
|
|
kfree(config);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int query_gt_list(struct xe_device *xe, struct drm_xe_device_query *query)
|
|
{
|
|
struct xe_gt *gt;
|
|
size_t size = sizeof(struct drm_xe_query_gt_list) +
|
|
xe->info.gt_count * sizeof(struct drm_xe_gt);
|
|
struct drm_xe_query_gt_list __user *query_ptr =
|
|
u64_to_user_ptr(query->data);
|
|
struct drm_xe_query_gt_list *gt_list;
|
|
int iter = 0;
|
|
u8 id;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
gt_list = kzalloc(size, GFP_KERNEL);
|
|
if (!gt_list)
|
|
return -ENOMEM;
|
|
|
|
gt_list->num_gt = xe->info.gt_count;
|
|
|
|
for_each_gt(gt, xe, id) {
|
|
if (xe_gt_is_media_type(gt))
|
|
gt_list->gt_list[iter].type = DRM_XE_QUERY_GT_TYPE_MEDIA;
|
|
else
|
|
gt_list->gt_list[iter].type = DRM_XE_QUERY_GT_TYPE_MAIN;
|
|
gt_list->gt_list[iter].tile_id = gt_to_tile(gt)->id;
|
|
gt_list->gt_list[iter].gt_id = gt->info.id;
|
|
gt_list->gt_list[iter].reference_clock = gt->info.reference_clock;
|
|
/*
|
|
* The mem_regions indexes in the mask below need to
|
|
* directly identify the struct
|
|
* drm_xe_query_mem_regions' instance constructed at
|
|
* query_mem_regions()
|
|
*
|
|
* For our current platforms:
|
|
* Bit 0 -> System Memory
|
|
* Bit 1 -> VRAM0 on Tile0
|
|
* Bit 2 -> VRAM1 on Tile1
|
|
* However the uAPI is generic and it's userspace's
|
|
* responsibility to check the mem_class, without any
|
|
* assumption.
|
|
*/
|
|
if (!IS_DGFX(xe))
|
|
gt_list->gt_list[iter].near_mem_regions = 0x1;
|
|
else
|
|
gt_list->gt_list[iter].near_mem_regions =
|
|
BIT(gt_to_tile(gt)->mem.vram->id) << 1;
|
|
gt_list->gt_list[iter].far_mem_regions = xe->info.mem_region_mask ^
|
|
gt_list->gt_list[iter].near_mem_regions;
|
|
|
|
gt_list->gt_list[iter].ip_ver_major =
|
|
REG_FIELD_GET(GMD_ID_ARCH_MASK, gt->info.gmdid);
|
|
gt_list->gt_list[iter].ip_ver_minor =
|
|
REG_FIELD_GET(GMD_ID_RELEASE_MASK, gt->info.gmdid);
|
|
gt_list->gt_list[iter].ip_ver_rev =
|
|
REG_FIELD_GET(GMD_ID_REVID, gt->info.gmdid);
|
|
|
|
iter++;
|
|
}
|
|
|
|
if (copy_to_user(query_ptr, gt_list, size)) {
|
|
kfree(gt_list);
|
|
return -EFAULT;
|
|
}
|
|
kfree(gt_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int query_hwconfig(struct xe_device *xe,
|
|
struct drm_xe_device_query *query)
|
|
{
|
|
struct xe_gt *gt = xe_root_mmio_gt(xe);
|
|
size_t size = xe_guc_hwconfig_size(>->uc.guc);
|
|
void __user *query_ptr = u64_to_user_ptr(query->data);
|
|
void *hwconfig;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
hwconfig = kzalloc(size, GFP_KERNEL);
|
|
if (!hwconfig)
|
|
return -ENOMEM;
|
|
|
|
xe_guc_hwconfig_copy(>->uc.guc, hwconfig);
|
|
|
|
if (copy_to_user(query_ptr, hwconfig, size)) {
|
|
kfree(hwconfig);
|
|
return -EFAULT;
|
|
}
|
|
kfree(hwconfig);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t calc_topo_query_size(struct xe_device *xe)
|
|
{
|
|
struct xe_gt *gt;
|
|
size_t query_size = 0;
|
|
int id;
|
|
|
|
for_each_gt(gt, xe, id) {
|
|
query_size += 3 * sizeof(struct drm_xe_query_topology_mask) +
|
|
sizeof_field(struct xe_gt, fuse_topo.g_dss_mask) +
|
|
sizeof_field(struct xe_gt, fuse_topo.c_dss_mask) +
|
|
sizeof_field(struct xe_gt, fuse_topo.eu_mask_per_dss);
|
|
|
|
/* L3bank mask may not be available for some GTs */
|
|
if (xe_gt_topology_report_l3(gt))
|
|
query_size += sizeof(struct drm_xe_query_topology_mask) +
|
|
sizeof_field(struct xe_gt, fuse_topo.l3_bank_mask);
|
|
}
|
|
|
|
return query_size;
|
|
}
|
|
|
|
static int copy_mask(void __user **ptr,
|
|
struct drm_xe_query_topology_mask *topo,
|
|
void *mask, size_t mask_size)
|
|
{
|
|
topo->num_bytes = mask_size;
|
|
|
|
if (copy_to_user(*ptr, topo, sizeof(*topo)))
|
|
return -EFAULT;
|
|
*ptr += sizeof(topo);
|
|
|
|
if (copy_to_user(*ptr, mask, mask_size))
|
|
return -EFAULT;
|
|
*ptr += mask_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int query_gt_topology(struct xe_device *xe,
|
|
struct drm_xe_device_query *query)
|
|
{
|
|
void __user *query_ptr = u64_to_user_ptr(query->data);
|
|
size_t size = calc_topo_query_size(xe);
|
|
struct drm_xe_query_topology_mask topo;
|
|
struct xe_gt *gt;
|
|
int id;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for_each_gt(gt, xe, id) {
|
|
int err;
|
|
|
|
topo.gt_id = id;
|
|
|
|
topo.type = DRM_XE_TOPO_DSS_GEOMETRY;
|
|
err = copy_mask(&query_ptr, &topo, gt->fuse_topo.g_dss_mask,
|
|
sizeof(gt->fuse_topo.g_dss_mask));
|
|
if (err)
|
|
return err;
|
|
|
|
topo.type = DRM_XE_TOPO_DSS_COMPUTE;
|
|
err = copy_mask(&query_ptr, &topo, gt->fuse_topo.c_dss_mask,
|
|
sizeof(gt->fuse_topo.c_dss_mask));
|
|
if (err)
|
|
return err;
|
|
|
|
/*
|
|
* If the kernel doesn't have a way to obtain a correct L3bank
|
|
* mask, then it's better to omit L3 from the query rather than
|
|
* reporting bogus or zeroed information to userspace.
|
|
*/
|
|
if (xe_gt_topology_report_l3(gt)) {
|
|
topo.type = DRM_XE_TOPO_L3_BANK;
|
|
err = copy_mask(&query_ptr, &topo, gt->fuse_topo.l3_bank_mask,
|
|
sizeof(gt->fuse_topo.l3_bank_mask));
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
topo.type = gt->fuse_topo.eu_type == XE_GT_EU_TYPE_SIMD16 ?
|
|
DRM_XE_TOPO_SIMD16_EU_PER_DSS :
|
|
DRM_XE_TOPO_EU_PER_DSS;
|
|
err = copy_mask(&query_ptr, &topo,
|
|
gt->fuse_topo.eu_mask_per_dss,
|
|
sizeof(gt->fuse_topo.eu_mask_per_dss));
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
query_uc_fw_version(struct xe_device *xe, struct drm_xe_device_query *query)
|
|
{
|
|
struct drm_xe_query_uc_fw_version __user *query_ptr = u64_to_user_ptr(query->data);
|
|
size_t size = sizeof(struct drm_xe_query_uc_fw_version);
|
|
struct drm_xe_query_uc_fw_version resp;
|
|
struct xe_uc_fw_version *version = NULL;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_from_user(&resp, query_ptr, size))
|
|
return -EFAULT;
|
|
|
|
if (XE_IOCTL_DBG(xe, resp.pad || resp.pad2 || resp.reserved))
|
|
return -EINVAL;
|
|
|
|
switch (resp.uc_type) {
|
|
case XE_QUERY_UC_TYPE_GUC_SUBMISSION: {
|
|
struct xe_guc *guc = &xe->tiles[0].primary_gt->uc.guc;
|
|
|
|
version = &guc->fw.versions.found[XE_UC_FW_VER_COMPATIBILITY];
|
|
break;
|
|
}
|
|
case XE_QUERY_UC_TYPE_HUC: {
|
|
struct xe_gt *media_gt = NULL;
|
|
struct xe_huc *huc;
|
|
|
|
if (MEDIA_VER(xe) >= 13) {
|
|
struct xe_tile *tile;
|
|
u8 gt_id;
|
|
|
|
for_each_tile(tile, xe, gt_id) {
|
|
if (tile->media_gt) {
|
|
media_gt = tile->media_gt;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
media_gt = xe->tiles[0].primary_gt;
|
|
}
|
|
|
|
if (!media_gt)
|
|
break;
|
|
|
|
huc = &media_gt->uc.huc;
|
|
if (huc->fw.status == XE_UC_FIRMWARE_RUNNING)
|
|
version = &huc->fw.versions.found[XE_UC_FW_VER_RELEASE];
|
|
break;
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (version) {
|
|
resp.branch_ver = 0;
|
|
resp.major_ver = version->major;
|
|
resp.minor_ver = version->minor;
|
|
resp.patch_ver = version->patch;
|
|
} else {
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (copy_to_user(query_ptr, &resp, size))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t calc_oa_unit_query_size(struct xe_device *xe)
|
|
{
|
|
size_t size = sizeof(struct drm_xe_query_oa_units);
|
|
struct xe_gt *gt;
|
|
int i, id;
|
|
|
|
for_each_gt(gt, xe, id) {
|
|
for (i = 0; i < gt->oa.num_oa_units; i++) {
|
|
size += sizeof(struct drm_xe_oa_unit);
|
|
size += gt->oa.oa_unit[i].num_engines *
|
|
sizeof(struct drm_xe_engine_class_instance);
|
|
}
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static int query_oa_units(struct xe_device *xe,
|
|
struct drm_xe_device_query *query)
|
|
{
|
|
void __user *query_ptr = u64_to_user_ptr(query->data);
|
|
size_t size = calc_oa_unit_query_size(xe);
|
|
struct drm_xe_query_oa_units *qoa;
|
|
enum xe_hw_engine_id hwe_id;
|
|
struct drm_xe_oa_unit *du;
|
|
struct xe_hw_engine *hwe;
|
|
struct xe_oa_unit *u;
|
|
int gt_id, i, j, ret;
|
|
struct xe_gt *gt;
|
|
u8 *pdu;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
qoa = kzalloc(size, GFP_KERNEL);
|
|
if (!qoa)
|
|
return -ENOMEM;
|
|
|
|
pdu = (u8 *)&qoa->oa_units[0];
|
|
for_each_gt(gt, xe, gt_id) {
|
|
for (i = 0; i < gt->oa.num_oa_units; i++) {
|
|
u = >->oa.oa_unit[i];
|
|
du = (struct drm_xe_oa_unit *)pdu;
|
|
|
|
du->oa_unit_id = u->oa_unit_id;
|
|
du->oa_unit_type = u->type;
|
|
du->oa_timestamp_freq = xe_oa_timestamp_frequency(gt);
|
|
du->capabilities = DRM_XE_OA_CAPS_BASE | DRM_XE_OA_CAPS_SYNCS |
|
|
DRM_XE_OA_CAPS_OA_BUFFER_SIZE |
|
|
DRM_XE_OA_CAPS_WAIT_NUM_REPORTS |
|
|
DRM_XE_OA_CAPS_OAM;
|
|
j = 0;
|
|
for_each_hw_engine(hwe, gt, hwe_id) {
|
|
if (!xe_hw_engine_is_reserved(hwe) &&
|
|
xe_oa_unit_id(hwe) == u->oa_unit_id) {
|
|
du->eci[j].engine_class =
|
|
xe_to_user_engine_class[hwe->class];
|
|
du->eci[j].engine_instance = hwe->logical_instance;
|
|
du->eci[j].gt_id = gt->info.id;
|
|
j++;
|
|
}
|
|
}
|
|
du->num_engines = j;
|
|
pdu += sizeof(*du) + j * sizeof(du->eci[0]);
|
|
qoa->num_oa_units++;
|
|
}
|
|
}
|
|
|
|
ret = copy_to_user(query_ptr, qoa, size);
|
|
kfree(qoa);
|
|
|
|
return ret ? -EFAULT : 0;
|
|
}
|
|
|
|
static int query_pxp_status(struct xe_device *xe, struct drm_xe_device_query *query)
|
|
{
|
|
struct drm_xe_query_pxp_status __user *query_ptr = u64_to_user_ptr(query->data);
|
|
size_t size = sizeof(struct drm_xe_query_pxp_status);
|
|
struct drm_xe_query_pxp_status resp = { 0 };
|
|
int ret;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = xe_pxp_get_readiness_status(xe->pxp);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
resp.status = ret;
|
|
resp.supported_session_types = BIT(DRM_XE_PXP_TYPE_HWDRM);
|
|
|
|
if (copy_to_user(query_ptr, &resp, size))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int query_eu_stall(struct xe_device *xe,
|
|
struct drm_xe_device_query *query)
|
|
{
|
|
void __user *query_ptr = u64_to_user_ptr(query->data);
|
|
struct drm_xe_query_eu_stall *info;
|
|
size_t size, array_size;
|
|
const u64 *rates;
|
|
u32 num_rates;
|
|
int ret;
|
|
|
|
if (!xe_eu_stall_supported_on_platform(xe))
|
|
return -ENODEV;
|
|
|
|
array_size = xe_eu_stall_get_sampling_rates(&num_rates, &rates);
|
|
size = sizeof(struct drm_xe_query_eu_stall) + array_size;
|
|
|
|
if (query->size == 0) {
|
|
query->size = size;
|
|
return 0;
|
|
} else if (XE_IOCTL_DBG(xe, query->size != size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
info = kzalloc(size, GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
info->num_sampling_rates = num_rates;
|
|
info->capabilities = DRM_XE_EU_STALL_CAPS_BASE;
|
|
info->record_size = xe_eu_stall_data_record_size(xe);
|
|
info->per_xecore_buf_size = xe_eu_stall_get_per_xecore_buf_size();
|
|
memcpy(info->sampling_rates, rates, array_size);
|
|
|
|
ret = copy_to_user(query_ptr, info, size);
|
|
kfree(info);
|
|
|
|
return ret ? -EFAULT : 0;
|
|
}
|
|
|
|
static int (* const xe_query_funcs[])(struct xe_device *xe,
|
|
struct drm_xe_device_query *query) = {
|
|
query_engines,
|
|
query_mem_regions,
|
|
query_config,
|
|
query_gt_list,
|
|
query_hwconfig,
|
|
query_gt_topology,
|
|
query_engine_cycles,
|
|
query_uc_fw_version,
|
|
query_oa_units,
|
|
query_pxp_status,
|
|
query_eu_stall,
|
|
};
|
|
|
|
int xe_query_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
struct xe_device *xe = to_xe_device(dev);
|
|
struct drm_xe_device_query *query = data;
|
|
u32 idx;
|
|
|
|
if (XE_IOCTL_DBG(xe, query->extensions) ||
|
|
XE_IOCTL_DBG(xe, query->reserved[0] || query->reserved[1]))
|
|
return -EINVAL;
|
|
|
|
if (XE_IOCTL_DBG(xe, query->query >= ARRAY_SIZE(xe_query_funcs)))
|
|
return -EINVAL;
|
|
|
|
idx = array_index_nospec(query->query, ARRAY_SIZE(xe_query_funcs));
|
|
if (XE_IOCTL_DBG(xe, !xe_query_funcs[idx]))
|
|
return -EINVAL;
|
|
|
|
return xe_query_funcs[idx](xe, query);
|
|
}
|