// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2024-2026 Intel Corporation */ #include #include #include #include #include "ivpu_drv.h" #include "ivpu_gem.h" #include "ivpu_fw.h" #include "ivpu_hw.h" #include "ivpu_sysfs.h" /** * DOC: npu_busy_time_us * * npu_busy_time_us is the time that the device spent executing jobs. * The time is counted when and only when there are jobs submitted to firmware. * * This time can be used to measure the utilization of NPU, either by calculating * npu_busy_time_us difference between two timepoints (i.e. measuring the time * that the NPU was active during some workload) or monitoring utilization percentage * by reading npu_busy_time_us periodically. * * When reading the value periodically, it shouldn't be read too often as it may have * an impact on job submission performance. Recommended period is 1 second. */ static ssize_t npu_busy_time_us_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); ktime_t total, now = 0; mutex_lock(&vdev->submitted_jobs_lock); total = vdev->busy_time; if (!xa_empty(&vdev->submitted_jobs_xa)) now = ktime_sub(ktime_get(), vdev->busy_start_ts); mutex_unlock(&vdev->submitted_jobs_lock); return sysfs_emit(buf, "%lld\n", ktime_to_us(ktime_add(total, now))); } static DEVICE_ATTR_RO(npu_busy_time_us); /** * DOC: npu_memory_utilization * * The npu_memory_utilization is used to report in bytes a current NPU memory utilization. * */ static ssize_t npu_memory_utilization_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); struct ivpu_bo *bo; u64 total_npu_memory = 0; mutex_lock(&vdev->bo_list_lock); list_for_each_entry(bo, &vdev->bo_list, bo_list_node) if (ivpu_bo_is_resident(bo)) total_npu_memory += ivpu_bo_size(bo); mutex_unlock(&vdev->bo_list_lock); return sysfs_emit(buf, "%lld\n", total_npu_memory); } static DEVICE_ATTR_RO(npu_memory_utilization); /** * DOC: sched_mode * * The sched_mode is used to report current NPU scheduling mode. * * It returns following strings: * - "HW" - Hardware Scheduler mode * - "OS" - Operating System Scheduler mode * */ static ssize_t sched_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); return sysfs_emit(buf, "%s\n", vdev->fw->sched_mode ? "HW" : "OS"); } static DEVICE_ATTR_RO(sched_mode); /** * DOC: NPU frequency control and information * * Hardware frequency capabilities: * freq/hw_min_freq - Minimum frequency supported by the NPU hardware. * freq/hw_max_freq - Maximum frequency supported by the NPU hardware. * freq/hw_efficient_freq - Most efficient operating frequency for the NPU. * * Configurable frequency limits (50XX devices and newer): * freq/set_min_freq - Configured minimum operating frequency. * freq/set_max_freq - Configured maximum operating frequency. * * Clamping behavior: Values written to set_min_freq and set_max_freq are * clamped to hardware limits. If set_min_freq exceeds set_max_freq, the driver * clamps set_min_freq to set_max_freq when selecting the operating frequency. * * Current operating frequency: * freq/current_freq - Current frequency in MHz. Valid only when the device is * active; returns 0 when idle. May be lower than the requested range due to * power or thermal constraints. * * Legacy attributes (backward compatibility): * npu_max_frequency_mhz - Alias for freq/hw_max_freq. * npu_current_frequency_mhz - Alias for freq/current_freq. */ static ssize_t hw_min_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz = ivpu_hw_btrs_pll_ratio_to_mhz(vdev, vdev->hw->pll.min_ratio); return sysfs_emit(buf, "%u\n", freq_mhz); } static DEVICE_ATTR_RO(hw_min_freq); static ssize_t hw_efficient_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz = ivpu_hw_btrs_pll_ratio_to_mhz(vdev, vdev->hw->pll.pn_ratio); return sysfs_emit(buf, "%u\n", freq_mhz); } static DEVICE_ATTR_RO(hw_efficient_freq); static ssize_t hw_max_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz = ivpu_hw_btrs_pll_ratio_to_mhz(vdev, vdev->hw->pll.max_ratio); return sysfs_emit(buf, "%u\n", freq_mhz); } static DEVICE_ATTR_RO(hw_max_freq); static ssize_t set_min_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz = ivpu_hw_btrs_pll_ratio_to_mhz(vdev, vdev->hw->pll.cfg_min_ratio); return sysfs_emit(buf, "%u\n", freq_mhz); } static ssize_t set_min_freq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz; int ret; ret = kstrtou32(buf, 10, &freq_mhz); if (ret) return ret; ret = ivpu_hw_btrs_cfg_min_freq_set(vdev, freq_mhz); if (ret) return ret; return count; } static DEVICE_ATTR_RW(set_min_freq); static ssize_t set_max_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz = ivpu_hw_btrs_pll_ratio_to_mhz(vdev, vdev->hw->pll.cfg_max_ratio); return sysfs_emit(buf, "%u\n", freq_mhz); } static ssize_t set_max_freq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz; int ret; ret = kstrtou32(buf, 10, &freq_mhz); if (ret) return ret; /* Convert MHz to Hz and set max frequency */ ret = ivpu_hw_btrs_cfg_max_freq_set(vdev, freq_mhz); if (ret) return ret; return count; } static DEVICE_ATTR_RW(set_max_freq); static ssize_t current_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_device *drm = dev_get_drvdata(dev); struct ivpu_device *vdev = to_ivpu_device(drm); u32 freq_mhz = 0; /* Read frequency only if device is active, otherwise frequency is 0 */ if (pm_runtime_get_if_active(vdev->drm.dev) > 0) { freq_mhz = ivpu_hw_btrs_current_freq_get(vdev); pm_runtime_put_autosuspend(vdev->drm.dev); } return sysfs_emit(buf, "%u\n", freq_mhz); } static DEVICE_ATTR_RO(current_freq); /* Alias to current_freq for legacy compat */ static struct device_attribute dev_attr_npu_max_frequency_mhz = __ATTR(npu_max_frequency_mhz, 0444, hw_max_freq_show, NULL); static struct device_attribute dev_attr_npu_current_frequency_mhz = __ATTR(npu_current_frequency_mhz, 0444, current_freq_show, NULL); static struct attribute *ivpu_freq_attrs[] = { &dev_attr_hw_min_freq.attr, &dev_attr_hw_efficient_freq.attr, &dev_attr_hw_max_freq.attr, &dev_attr_current_freq.attr, NULL, }; static struct attribute_group ivpu_freq_attr_group = { .name = "freq", .attrs = ivpu_freq_attrs, }; static struct attribute *ivpu_dev_attrs[] = { &dev_attr_npu_busy_time_us.attr, &dev_attr_npu_memory_utilization.attr, &dev_attr_sched_mode.attr, &dev_attr_npu_max_frequency_mhz.attr, &dev_attr_npu_current_frequency_mhz.attr, NULL, }; static struct attribute_group ivpu_dev_attr_group = { .attrs = ivpu_dev_attrs, }; void ivpu_sysfs_init(struct ivpu_device *vdev) { int ret; ret = devm_device_add_group(vdev->drm.dev, &ivpu_dev_attr_group); if (ret) { ivpu_warn(vdev, "Failed to add sysfs group to device, ret %d", ret); return; } ret = devm_device_add_group(vdev->drm.dev, &ivpu_freq_attr_group); if (ret) { ivpu_warn(vdev, "Failed to add sysfs freq group, ret %d", ret); return; } if (ivpu_hw_ip_gen(vdev) >= IVPU_HW_IP_50XX) { ret = sysfs_add_file_to_group(&vdev->drm.dev->kobj, &dev_attr_set_min_freq.attr, "freq"); if (ret) { ivpu_warn(vdev, "Failed to add sysfs set_min_freq to device, ret %d", ret); return; } ret = sysfs_add_file_to_group(&vdev->drm.dev->kobj, &dev_attr_set_max_freq.attr, "freq"); if (ret) { ivpu_warn(vdev, "Failed to add sysfs set_max_freq to device, ret %d", ret); return; } } }