There is no way to tell how much of the NPU a client uses, or whether its jobs reach more than one core. Implement the DRM usage stats: account the time each job occupies a core and report it per client as drm-engine-npu<n>, along with drm_show_memory_stats() for the BOs.
Jobs can outlive the file that submitted them, so the counters are refcounted and each job holds a reference. The time covers the whole period a job owns a core, so it also includes handling the completion interrupt and the gaps between the tasks of a multi-task job. The array is sized by rdev->max_cores, the number of core slots, not by the number of cores bound when the file is opened. A core can still be probing while a client opens the device, and the IRQ handler indexes the array with core->index, which stays valid when a core is unbound. A slot with no core bound reads as zero. For example, teflon running a model two operations deep 3001 times gives: drm-engine-npu0: 22719385 ns drm-engine-npu1: 23345296 ns drm-engine-npu2: 0 ns The NPU is busy 15.6% of the wall clock time and one core is never used. Signed-off-by: Sidong Yang <[email protected]> Reviewed-by: Gyeyoung Baek <[email protected]> Tested-by: Gyeyoung Baek <[email protected]> --- v3: Add a rocket_engine_usage_create() helper for the counter allocation in rocket_open(), to pair with rocket_engine_usage_destroy() (Gyeyoung Baek). Still depends on commit 6eff1d69dae8 ("accel/rocket: search every core slot when a core is removed") for rdev->max_cores. v2: Size the per-core counters by rdev->max_cores, the number of core slots, instead of the number of cores bound when the file is opened. Sashiko reported the out-of-bounds read and Gyeyoung Baek reproduced it with KASAN. This depends on commit 6eff1d69dae8 ("accel/rocket: search every core slot when a core is removed"), which added rdev->max_cores. It is in mainline through drm-misc-fixes but not yet in drm-misc-next. Link to v2: https://lore.kernel.org/all/[email protected]/ Link to v1: https://lore.kernel.org/all/[email protected]/ drivers/accel/rocket/rocket_drv.c | 71 ++++++++++++++++++++++++++++++- drivers/accel/rocket/rocket_drv.h | 14 ++++++ drivers/accel/rocket/rocket_job.c | 7 +++ drivers/accel/rocket/rocket_job.h | 4 ++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocket_drv.c index 2bcfe4ab3c68..d9fd05e9b1f3 100644 --- a/drivers/accel/rocket/rocket_drv.c +++ b/drivers/accel/rocket/rocket_drv.c @@ -3,8 +3,10 @@ #include <drm/drm_accel.h> #include <drm/drm_drv.h> +#include <drm/drm_file.h> #include <drm/drm_gem.h> #include <drm/drm_ioctl.h> +#include <drm/drm_print.h> #include <drm/rocket_accel.h> #include <linux/clk.h> #include <linux/err.h> @@ -68,6 +70,38 @@ rocket_iommu_domain_put(struct rocket_iommu_domain *domain) kref_put(&domain->kref, rocket_iommu_domain_destroy); } +static struct rocket_engine_usage * +rocket_engine_usage_create(struct rocket_device *rdev) +{ + struct rocket_engine_usage *usage; + + usage = kzalloc_flex(*usage, elapsed_ns, rdev->max_cores); + if (!usage) + return NULL; + kref_init(&usage->kref); + + return usage; +} + +static void +rocket_engine_usage_destroy(struct kref *kref) +{ + kfree(container_of(kref, struct rocket_engine_usage, kref)); +} + +struct rocket_engine_usage * +rocket_engine_usage_get(struct rocket_file_priv *rocket_priv) +{ + kref_get(&rocket_priv->engine_usage->kref); + return rocket_priv->engine_usage; +} + +void +rocket_engine_usage_put(struct rocket_engine_usage *usage) +{ + kref_put(&usage->kref, rocket_engine_usage_destroy); +} + static int rocket_open(struct drm_device *dev, struct drm_file *file) { @@ -86,10 +120,17 @@ rocket_open(struct drm_device *dev, struct drm_file *file) } rocket_priv->rdev = rdev; + + rocket_priv->engine_usage = rocket_engine_usage_create(rdev); + if (!rocket_priv->engine_usage) { + ret = -ENOMEM; + goto err_free; + } + rocket_priv->domain = rocket_iommu_domain_create(rdev->cores[0].dev); if (IS_ERR(rocket_priv->domain)) { ret = PTR_ERR(rocket_priv->domain); - goto err_free; + goto err_put_engine_usage; } file->driver_priv = rocket_priv; @@ -109,6 +150,8 @@ rocket_open(struct drm_device *dev, struct drm_file *file) mutex_destroy(&rocket_priv->mm_lock); drm_mm_takedown(&rocket_priv->mm); rocket_iommu_domain_put(rocket_priv->domain); +err_put_engine_usage: + rocket_engine_usage_put(rocket_priv->engine_usage); err_free: kfree(rocket_priv); err_put_mod: @@ -125,6 +168,7 @@ rocket_postclose(struct drm_device *dev, struct drm_file *file) mutex_destroy(&rocket_priv->mm_lock); drm_mm_takedown(&rocket_priv->mm); rocket_iommu_domain_put(rocket_priv->domain); + rocket_engine_usage_put(rocket_priv->engine_usage); kfree(rocket_priv); module_put(THIS_MODULE); } @@ -139,7 +183,29 @@ static const struct drm_ioctl_desc rocket_drm_driver_ioctls[] = { ROCKET_IOCTL(FINI_BO, fini_bo), }; -DEFINE_DRM_ACCEL_FOPS(rocket_accel_driver_fops); +static void rocket_show_fdinfo(struct drm_printer *p, struct drm_file *file) +{ + struct rocket_device *rdev = to_rocket_device(file->minor->dev); + struct rocket_file_priv *rocket_priv = file->driver_priv; + unsigned int core; + + /* + * These are estimates: the time is taken around the whole period a job + * occupies a core, so it also covers handling the completion interrupt + * and, for a job made of several tasks, the gaps between them. + */ + for (core = 0; core < rdev->max_cores; core++) + drm_printf(p, "drm-engine-npu%u:\t%llu ns\n", core, + rocket_priv->engine_usage->elapsed_ns[core]); + + drm_show_memory_stats(p, file); +} + +static const struct file_operations rocket_accel_driver_fops = { + .owner = THIS_MODULE, + DRM_ACCEL_FOPS, + .show_fdinfo = drm_show_fdinfo, +}; /* * Rocket driver version: @@ -150,6 +216,7 @@ static const struct drm_driver rocket_drm_driver = { .open = rocket_open, .postclose = rocket_postclose, .gem_create_object = rocket_gem_create_object, + .show_fdinfo = rocket_show_fdinfo, .ioctls = rocket_drm_driver_ioctls, .num_ioctls = ARRAY_SIZE(rocket_drm_driver_ioctls), .fops = &rocket_accel_driver_fops, diff --git a/drivers/accel/rocket/rocket_drv.h b/drivers/accel/rocket/rocket_drv.h index 2c673bb99ccc..eff5c2ac54d1 100644 --- a/drivers/accel/rocket/rocket_drv.h +++ b/drivers/accel/rocket/rocket_drv.h @@ -16,6 +16,15 @@ struct rocket_iommu_domain { struct kref kref; }; +/* + * Time each core has spent running a client's jobs, in nanoseconds, indexed by + * core. Jobs hold a reference, as they can outlive the file that submitted them. + */ +struct rocket_engine_usage { + struct kref kref; + u64 elapsed_ns[]; +}; + struct rocket_file_priv { struct rocket_device *rdev; @@ -24,9 +33,14 @@ struct rocket_file_priv { struct mutex mm_lock; struct drm_sched_entity sched_entity; + + struct rocket_engine_usage *engine_usage; }; struct rocket_iommu_domain *rocket_iommu_domain_get(struct rocket_file_priv *rocket_priv); void rocket_iommu_domain_put(struct rocket_iommu_domain *domain); +struct rocket_engine_usage *rocket_engine_usage_get(struct rocket_file_priv *rocket_priv); +void rocket_engine_usage_put(struct rocket_engine_usage *usage); + #endif diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c index ce994748dd5d..c0ffbccfb6cc 100644 --- a/drivers/accel/rocket/rocket_job.c +++ b/drivers/accel/rocket/rocket_job.c @@ -13,6 +13,7 @@ #include <linux/iommu.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/sched/clock.h> #include "rocket_core.h" #include "rocket_device.h" @@ -265,6 +266,7 @@ static void rocket_job_cleanup(struct kref *ref) unsigned int i; rocket_iommu_domain_put(job->domain); + rocket_engine_usage_put(job->engine_usage); dma_fence_put(job->done_fence); dma_fence_put(job->inference_done_fence); @@ -351,6 +353,7 @@ static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job) scoped_guard(mutex, &core->job_lock) { core->in_flight_job = job; + job->start_ns = local_clock(); rocket_job_hw_submit(core, job); } @@ -379,6 +382,9 @@ static void rocket_job_handle_irq(struct rocket_core *core) return; } + core->in_flight_job->engine_usage->elapsed_ns[core->index] += + local_clock() - core->in_flight_job->start_ns; + iommu_detach_group(NULL, iommu_group_get(core->dev)); dma_fence_signal(core->in_flight_job->done_fence); pm_runtime_put_autosuspend(core->dev); @@ -587,6 +593,7 @@ static int rocket_ioctl_submit_job(struct drm_device *dev, struct drm_file *file rjob->rdev = rdev; rjob->domain = rocket_iommu_domain_get(file_priv); + rjob->engine_usage = rocket_engine_usage_get(file_priv); ret = drm_sched_job_init(&rjob->base, &file_priv->sched_entity, diff --git a/drivers/accel/rocket/rocket_job.h b/drivers/accel/rocket/rocket_job.h index 4ae00feec3b9..14aa78aebc99 100644 --- a/drivers/accel/rocket/rocket_job.h +++ b/drivers/accel/rocket/rocket_job.h @@ -38,6 +38,10 @@ struct rocket_job { struct rocket_iommu_domain *domain; + /* When the job was handed to a core, and where to account the time */ + u64 start_ns; + struct rocket_engine_usage *engine_usage; + struct kref refcount; }; base-commit: a1e63ab695ad76850ab9787b9eb6aab695dc2965 prerequisite-patch-id: 519bcdfdde80d902309c8346f749ebc4bb6b29c0 -- 2.53.0
