/*
|
*
|
* (C) COPYRIGHT 2016, 2017 ARM Limited. All rights reserved.
|
*
|
* This program is free software and is provided to you under the terms of the
|
* GNU General Public License version 2 as published by the Free Software
|
* Foundation, and any use by you of this program is subject to the terms
|
* of such GNU licence.
|
*
|
* A copy of the licence is included with the program, and can also be obtained
|
* from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
* Boston, MA 02110-1301, USA.
|
*
|
*/
|
|
|
|
#include <linux/module.h>
|
#include <linux/delay.h>
|
#include <linux/interrupt.h>
|
|
#include "mali_kbase.h"
|
#include <midgard/backend/gpu/mali_kbase_device_internal.h>
|
|
#include <kutf/kutf_suite.h>
|
#include <kutf/kutf_utils.h>
|
|
/*
|
* This file contains the code which is used for measuring interrupt latency
|
* of the Mali GPU IRQ. In particular, function mali_kutf_irq_latency() is
|
* used with this purpose and it is called within KUTF framework - a kernel
|
* unit test framework. The measured latency provided by this test should
|
* be representative for the latency of the Mali JOB/MMU IRQs as well.
|
*/
|
|
/* KUTF test application pointer for this test */
|
struct kutf_application *irq_app;
|
|
/**
|
* struct kutf_irq_fixture data - test fixture used by the test functions.
|
* @kbdev: kbase device for the GPU.
|
*
|
*/
|
struct kutf_irq_fixture_data {
|
struct kbase_device *kbdev;
|
};
|
|
#define SEC_TO_NANO(s) ((s)*1000000000LL)
|
|
/* ID for the GPU IRQ */
|
#define GPU_IRQ_HANDLER 2
|
|
#define NR_TEST_IRQS 1000000
|
|
/* IRQ for the test to trigger. Currently MULTIPLE_GPU_FAULTS as we would not
|
* expect to see this in normal use (e.g., when Android is running). */
|
#define TEST_IRQ MULTIPLE_GPU_FAULTS
|
|
#define IRQ_TIMEOUT HZ
|
|
/* Kernel API for setting irq throttle hook callback and irq time in us*/
|
extern int kbase_set_custom_irq_handler(struct kbase_device *kbdev,
|
irq_handler_t custom_handler,
|
int irq_type);
|
extern irqreturn_t kbase_gpu_irq_handler(int irq, void *data);
|
|
static DECLARE_WAIT_QUEUE_HEAD(wait);
|
static bool triggered;
|
static u64 irq_time;
|
|
static void *kbase_untag(void *ptr)
|
{
|
return (void *)(((uintptr_t) ptr) & ~3);
|
}
|
|
/**
|
* kbase_gpu_irq_custom_handler - Custom IRQ throttle handler
|
* @irq: IRQ number
|
* @data: Data associated with this IRQ
|
*
|
* Return: state of the IRQ
|
*/
|
static irqreturn_t kbase_gpu_irq_custom_handler(int irq, void *data)
|
{
|
struct kbase_device *kbdev = kbase_untag(data);
|
u32 val;
|
|
val = kbase_reg_read(kbdev, GPU_CONTROL_REG(GPU_IRQ_STATUS), NULL);
|
if (val & TEST_IRQ) {
|
struct timespec64 tval;
|
|
ktime_get_real_ts64(&tval);
|
irq_time = SEC_TO_NANO(tval.tv_sec) + (tval.tv_nsec);
|
|
kbase_reg_write(kbdev, GPU_CONTROL_REG(GPU_IRQ_CLEAR), val,
|
NULL);
|
|
triggered = true;
|
wake_up(&wait);
|
|
return IRQ_HANDLED;
|
}
|
|
/* Trigger main irq handler */
|
return kbase_gpu_irq_handler(irq, data);
|
}
|
|
/**
|
* mali_kutf_irq_default_create_fixture() - Creates the fixture data required
|
* for all the tests in the irq suite.
|
* @context: KUTF context.
|
*
|
* Return: Fixture data created on success or NULL on failure
|
*/
|
static void *mali_kutf_irq_default_create_fixture(
|
struct kutf_context *context)
|
{
|
struct kutf_irq_fixture_data *data;
|
|
data = kutf_mempool_alloc(&context->fixture_pool,
|
sizeof(struct kutf_irq_fixture_data));
|
|
if (!data)
|
goto fail;
|
|
/* Acquire the kbase device */
|
data->kbdev = kbase_find_device(-1);
|
if (data->kbdev == NULL) {
|
kutf_test_fail(context, "Failed to find kbase device");
|
goto fail;
|
}
|
|
return data;
|
|
fail:
|
return NULL;
|
}
|
|
/**
|
* mali_kutf_irq_default_remove_fixture() - Destroy fixture data previously
|
* created by mali_kutf_irq_default_create_fixture.
|
*
|
* @context: KUTF context.
|
*/
|
static void mali_kutf_irq_default_remove_fixture(
|
struct kutf_context *context)
|
{
|
struct kutf_irq_fixture_data *data = context->fixture;
|
struct kbase_device *kbdev = data->kbdev;
|
|
kbase_release_device(kbdev);
|
}
|
|
/**
|
* mali_kutf_irq_latency() - measure GPU IRQ latency
|
* @context: kutf context within which to perform the test
|
*
|
* The test triggers IRQs manually, and measures the
|
* time between triggering the IRQ and the IRQ handler being executed.
|
*
|
* This is not a traditional test, in that the pass/fail status has little
|
* meaning (other than indicating that the IRQ handler executed at all). Instead
|
* the results are in the latencies provided with the test result. There is no
|
* meaningful pass/fail result that can be obtained here, instead the latencies
|
* are provided for manual analysis only.
|
*/
|
static void mali_kutf_irq_latency(struct kutf_context *context)
|
{
|
struct kutf_irq_fixture_data *data = context->fixture;
|
struct kbase_device *kbdev = data->kbdev;
|
u64 min_time = U64_MAX, max_time = 0, average_time = 0;
|
int i;
|
bool test_failed = false;
|
|
/* Force GPU to be powered */
|
kbase_pm_context_active(kbdev);
|
|
kbase_set_custom_irq_handler(kbdev, kbase_gpu_irq_custom_handler,
|
GPU_IRQ_HANDLER);
|
|
for (i = 0; i < NR_TEST_IRQS; i++) {
|
struct timespec64 tval;
|
u64 start_time;
|
int ret;
|
|
triggered = false;
|
ktime_get_real_ts64(&tval);
|
start_time = SEC_TO_NANO(tval.tv_sec) + (tval.tv_nsec);
|
|
/* Trigger fake IRQ */
|
kbase_reg_write(kbdev, GPU_CONTROL_REG(GPU_IRQ_RAWSTAT),
|
TEST_IRQ, NULL);
|
|
ret = wait_event_timeout(wait, triggered != false, IRQ_TIMEOUT);
|
|
if (ret == 0) {
|
kutf_test_fail(context, "Timed out waiting for IRQ\n");
|
test_failed = true;
|
break;
|
}
|
|
if ((irq_time - start_time) < min_time)
|
min_time = irq_time - start_time;
|
if ((irq_time - start_time) > max_time)
|
max_time = irq_time - start_time;
|
average_time += irq_time - start_time;
|
|
udelay(10);
|
}
|
|
/* Go back to default handler */
|
kbase_set_custom_irq_handler(kbdev, NULL, GPU_IRQ_HANDLER);
|
|
kbase_pm_context_idle(kbdev);
|
|
if (!test_failed) {
|
const char *results;
|
|
do_div(average_time, NR_TEST_IRQS);
|
results = kutf_dsprintf(&context->fixture_pool,
|
"Min latency = %lldns, Max latency = %lldns, Average latency = %lldns\n",
|
min_time, max_time, average_time);
|
kutf_test_pass(context, results);
|
}
|
}
|
|
/**
|
* Module entry point for this test.
|
*/
|
int mali_kutf_irq_test_main_init(void)
|
{
|
struct kutf_suite *suite;
|
|
irq_app = kutf_create_application("irq");
|
suite = kutf_create_suite(irq_app, "irq_default",
|
1, mali_kutf_irq_default_create_fixture,
|
mali_kutf_irq_default_remove_fixture);
|
|
kutf_add_test(suite, 0x0, "irq_latency",
|
mali_kutf_irq_latency);
|
return 0;
|
}
|
|
/**
|
* Module exit point for this test.
|
*/
|
void mali_kutf_irq_test_main_exit(void)
|
{
|
kutf_destroy_application(irq_app);
|
}
|
|
module_init(mali_kutf_irq_test_main_init);
|
module_exit(mali_kutf_irq_test_main_exit);
|
|
MODULE_LICENSE("GPL");
|
MODULE_AUTHOR("ARM Ltd.");
|
MODULE_VERSION("1.0");
|