blob: 4181b7f92db61343b0fd9019179bb32e0bc81cee [file] [log] [blame]
/*
*
* (C) COPYRIGHT 2016-2018 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* SPDX-License-Identifier: GPL-2.0
*
*/
#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));
if (val & TEST_IRQ) {
struct timespec tval;
getnstimeofday(&tval);
irq_time = SEC_TO_NANO(tval.tv_sec) + (tval.tv_nsec);
kbase_reg_write(kbdev, GPU_CONTROL_REG(GPU_IRQ_CLEAR), val);
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 timespec tval;
u64 start_time;
int ret;
triggered = false;
getnstimeofday(&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);
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");
if (NULL == irq_app) {
pr_warn("Creation of test application failed!\n");
return -ENOMEM;
}
suite = kutf_create_suite(irq_app, "irq_default",
1, mali_kutf_irq_default_create_fixture,
mali_kutf_irq_default_remove_fixture);
if (NULL == suite) {
pr_warn("Creation of test suite failed!\n");
kutf_destroy_application(irq_app);
return -ENOMEM;
}
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");