blob: 3d786cabd9c4b94143815a7fdf472ff6bceb2748 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2021-2022 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 license.
*
* 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.
*
*/
#include <mali_kbase.h>
#include <mali_kbase_hwcnt_gpu.h>
#include <mali_kbase_hwcnt_types.h>
#include <mali_kbase_hwcnt_backend.h>
#include <mali_kbase_hwcnt_watchdog_if.h>
#if IS_ENABLED(CONFIG_MALI_IS_FPGA) && !IS_ENABLED(CONFIG_MALI_NO_MALI)
/* Backend watch dog timer interval in milliseconds: 18 seconds. */
static const u32 hwcnt_backend_watchdog_timer_interval_ms = 18000;
#else
/* Backend watch dog timer interval in milliseconds: 1 second. */
static const u32 hwcnt_backend_watchdog_timer_interval_ms = 1000;
#endif /* IS_FPGA && !NO_MALI */
/*
* IDLE_BUFFER_EMPTY -> USER_DUMPING_BUFFER_EMPTY on dump_request.
* IDLE_BUFFER_EMPTY -> TIMER_DUMPING after
* hwcnt_backend_watchdog_timer_interval_ms
* milliseconds, if no dump_request has been
* called in the meantime.
* IDLE_BUFFER_FULL -> USER_DUMPING_BUFFER_FULL on dump_request.
* IDLE_BUFFER_FULL -> TIMER_DUMPING after
* hwcnt_backend_watchdog_timer_interval_ms
* milliseconds, if no dump_request has been
* called in the meantime.
* IDLE_BUFFER_FULL -> IDLE_BUFFER_EMPTY on dump_disable, upon discarding undumped
* counter values since the last dump_get.
* IDLE_BUFFER_EMPTY -> BUFFER_CLEARING on dump_clear, before calling job manager
* backend dump_clear.
* IDLE_BUFFER_FULL -> BUFFER_CLEARING on dump_clear, before calling job manager
* backend dump_clear.
* USER_DUMPING_BUFFER_EMPTY -> BUFFER_CLEARING on dump_clear, before calling job manager
* backend dump_clear.
* USER_DUMPING_BUFFER_FULL -> BUFFER_CLEARING on dump_clear, before calling job manager
* backend dump_clear.
* BUFFER_CLEARING -> IDLE_BUFFER_EMPTY on dump_clear, upon job manager backend
* dump_clear completion.
* TIMER_DUMPING -> IDLE_BUFFER_FULL on timer's callback completion.
* TIMER_DUMPING -> TIMER_DUMPING_USER_CLEAR on dump_clear, notifies the callback thread
* that there is no need for dumping the buffer
* anymore, and that the client will proceed
* clearing the buffer.
* TIMER_DUMPING_USER_CLEAR -> IDLE_BUFFER_EMPTY on timer's callback completion, when a user
* requested a dump_clear.
* TIMER_DUMPING -> TIMER_DUMPING_USER_REQUESTED on dump_request, when a client performs a
* dump request while the timer is dumping (the
* timer will perform the dump and (once
* completed) the client will retrieve the value
* from the buffer).
* TIMER_DUMPING_USER_REQUESTED -> IDLE_BUFFER_EMPTY on dump_get, when a timer completed and the
* user reads the periodic dump buffer.
* Any -> ERROR if the job manager backend returns an error
* (of any kind).
* USER_DUMPING_BUFFER_EMPTY -> IDLE_BUFFER_EMPTY on dump_get (performs get, ignores the
* periodic dump buffer and returns).
* USER_DUMPING_BUFFER_FULL -> IDLE_BUFFER_EMPTY on dump_get (performs get, accumulates with
* periodic dump buffer and returns).
*/
/** enum backend_watchdog_state State used to synchronize timer callbacks with the main thread.
* @HWCNT_JM_WD_ERROR: Received an error from the job manager backend calls.
* @HWCNT_JM_WD_IDLE_BUFFER_EMPTY: Initial state. Watchdog timer enabled, periodic dump buffer is
* empty.
* @HWCNT_JM_WD_IDLE_BUFFER_FULL: Watchdog timer enabled, periodic dump buffer is full.
* @HWCNT_JM_WD_BUFFER_CLEARING: The client is performing a dump clear. A concurrent timer callback
* thread should just ignore and reschedule another callback in
* hwcnt_backend_watchdog_timer_interval_ms milliseconds.
* @HWCNT_JM_WD_TIMER_DUMPING: The timer ran out. The callback is performing a periodic dump.
* @HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED: While the timer is performing a periodic dump, user
* requested a dump.
* @HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR: While the timer is performing a dump, user requested a
* dump_clear. The timer has to complete the periodic dump
* and clear buffer (internal and job manager backend).
* @HWCNT_JM_WD_USER_DUMPING_BUFFER_EMPTY: From IDLE state, user requested a dump. The periodic
* dump buffer is empty.
* @HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL: From IDLE state, user requested a dump. The periodic dump
* buffer is full.
*
* While the state machine is in HWCNT_JM_WD_TIMER_DUMPING*, only the timer callback thread is
* allowed to call the job manager backend layer.
*/
enum backend_watchdog_state {
HWCNT_JM_WD_ERROR,
HWCNT_JM_WD_IDLE_BUFFER_EMPTY,
HWCNT_JM_WD_IDLE_BUFFER_FULL,
HWCNT_JM_WD_BUFFER_CLEARING,
HWCNT_JM_WD_TIMER_DUMPING,
HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED,
HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR,
HWCNT_JM_WD_USER_DUMPING_BUFFER_EMPTY,
HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL,
};
/** enum wd_init_state - State machine for initialization / termination of the backend resources
*/
enum wd_init_state {
HWCNT_JM_WD_INIT_START,
HWCNT_JM_WD_INIT_ALLOC = HWCNT_JM_WD_INIT_START,
HWCNT_JM_WD_INIT_BACKEND,
HWCNT_JM_WD_INIT_ENABLE_MAP,
HWCNT_JM_WD_INIT_DUMP_BUFFER,
HWCNT_JM_WD_INIT_END
};
/**
* struct kbase_hwcnt_backend_jm_watchdog_info - Immutable information used to initialize an
* instance of the job manager watchdog backend.
* @jm_backend_iface: Hardware counter backend interface. This module extends
* this interface with a watchdog that performs regular
* dumps. The new interface this module provides complies
* with the old backend interface.
* @dump_watchdog_iface: Dump watchdog interface, used to periodically dump the
* hardware counter in case no reads are requested within
* a certain time, used to avoid hardware counter's buffer
* saturation.
*/
struct kbase_hwcnt_backend_jm_watchdog_info {
struct kbase_hwcnt_backend_interface *jm_backend_iface;
struct kbase_hwcnt_watchdog_interface *dump_watchdog_iface;
};
/**
* struct kbase_hwcnt_backend_jm_watchdog - An instance of the job manager watchdog backend.
* @info: Immutable information used to create the job manager watchdog backend.
* @jm_backend: Job manager's backend internal state. To be passed as argument during parent calls.
* @timeout_ms: Time period in milliseconds for hardware counters dumping.
* @wd_dump_buffer: Used to store periodic dumps done by a timer callback function. Contents are
* valid in state %HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED,
* %HWCNT_JM_WD_IDLE_BUFFER_FULL or %HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL.
* @wd_enable_map: Watchdog backend internal buffer mask, initialized during dump_enable copying
* the enable_map passed as argument.
* @wd_dump_timestamp: Holds the dumping timestamp for potential future client dump_request, filled
* during watchdog timer dumps.
* @watchdog_complete: Used for synchronization between watchdog dumper thread and client calls.
* @locked: Members protected from concurrent access by different threads.
* @locked.watchdog_lock: Lock used to access fields within this struct (that require mutual
* exclusion).
* @locked.is_enabled: If true then the wrapped job manager hardware counter backend and the
* watchdog timer are both enabled. If false then both are disabled (or soon
* will be). Races between enable and disable have undefined behavior.
* @locked.state: State used to synchronize timer callbacks with the main thread.
*/
struct kbase_hwcnt_backend_jm_watchdog {
const struct kbase_hwcnt_backend_jm_watchdog_info *info;
struct kbase_hwcnt_backend *jm_backend;
u32 timeout_ms;
struct kbase_hwcnt_dump_buffer wd_dump_buffer;
struct kbase_hwcnt_enable_map wd_enable_map;
u64 wd_dump_timestamp;
struct completion watchdog_complete;
struct {
spinlock_t watchdog_lock;
bool is_enabled;
enum backend_watchdog_state state;
} locked;
};
/* timer's callback function */
static void kbasep_hwcnt_backend_jm_watchdog_timer_callback(void *backend)
{
struct kbase_hwcnt_backend_jm_watchdog *wd_backend = backend;
unsigned long flags;
bool wd_accumulate;
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
if (!wd_backend->locked.is_enabled || wd_backend->locked.state == HWCNT_JM_WD_ERROR) {
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
return;
}
if (!(wd_backend->locked.state == HWCNT_JM_WD_IDLE_BUFFER_EMPTY ||
wd_backend->locked.state == HWCNT_JM_WD_IDLE_BUFFER_FULL)) {
/*resetting the timer. Calling modify on a disabled timer enables it.*/
wd_backend->info->dump_watchdog_iface->modify(
wd_backend->info->dump_watchdog_iface->timer, wd_backend->timeout_ms);
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
return;
}
/*start performing the dump*/
/* if there has been a previous timeout use accumulating dump_get()
* otherwise use non-accumulating to overwrite buffer
*/
wd_accumulate = (wd_backend->locked.state == HWCNT_JM_WD_IDLE_BUFFER_FULL);
wd_backend->locked.state = HWCNT_JM_WD_TIMER_DUMPING;
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
if (wd_backend->info->jm_backend_iface->dump_request(wd_backend->jm_backend,
&wd_backend->wd_dump_timestamp) ||
wd_backend->info->jm_backend_iface->dump_wait(wd_backend->jm_backend) ||
wd_backend->info->jm_backend_iface->dump_get(
wd_backend->jm_backend, &wd_backend->wd_dump_buffer, &wd_backend->wd_enable_map,
wd_accumulate)) {
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
WARN_ON(wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING &&
wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR &&
wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED);
wd_backend->locked.state = HWCNT_JM_WD_ERROR;
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
/* Unblock user if it's waiting. */
complete_all(&wd_backend->watchdog_complete);
return;
}
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
WARN_ON(wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING &&
wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR &&
wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED);
if (wd_backend->locked.state == HWCNT_JM_WD_TIMER_DUMPING) {
/* If there is no user request/clear, transit to HWCNT_JM_WD_IDLE_BUFFER_FULL
* to indicate timer dump is done and the buffer is full. If state changed to
* HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED or
* HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR then user will transit the state
* machine to next state.
*/
wd_backend->locked.state = HWCNT_JM_WD_IDLE_BUFFER_FULL;
}
if (wd_backend->locked.state != HWCNT_JM_WD_ERROR && wd_backend->locked.is_enabled) {
/* reset the timer to schedule another callback. Calling modify on a
* disabled timer enables it.
*/
/*The spin lock needs to be held in case the client calls dump_enable*/
wd_backend->info->dump_watchdog_iface->modify(
wd_backend->info->dump_watchdog_iface->timer, wd_backend->timeout_ms);
}
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
/* Unblock user if it's waiting. */
complete_all(&wd_backend->watchdog_complete);
}
/* helper methods, info structure creation and destruction*/
static struct kbase_hwcnt_backend_jm_watchdog_info *
kbasep_hwcnt_backend_jm_watchdog_info_create(struct kbase_hwcnt_backend_interface *backend_iface,
struct kbase_hwcnt_watchdog_interface *watchdog_iface)
{
struct kbase_hwcnt_backend_jm_watchdog_info *const info =
kmalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return NULL;
*info = (struct kbase_hwcnt_backend_jm_watchdog_info){ .jm_backend_iface = backend_iface,
.dump_watchdog_iface =
watchdog_iface };
return info;
}
/****** kbase_hwcnt_backend_interface implementation *******/
/* Job manager watchdog backend, implementation of kbase_hwcnt_backend_metadata_fn */
static const struct kbase_hwcnt_metadata *
kbasep_hwcnt_backend_jm_watchdog_metadata(const struct kbase_hwcnt_backend_info *info)
{
const struct kbase_hwcnt_backend_jm_watchdog_info *wd_info = (void *)info;
if (WARN_ON(!info))
return NULL;
return wd_info->jm_backend_iface->metadata(wd_info->jm_backend_iface->info);
}
static void
kbasep_hwcnt_backend_jm_watchdog_term_partial(struct kbase_hwcnt_backend_jm_watchdog *wd_backend,
enum wd_init_state state)
{
if (!wd_backend)
return;
/* disable timer thread to avoid concurrent access to shared resources */
wd_backend->info->dump_watchdog_iface->disable(
wd_backend->info->dump_watchdog_iface->timer);
/*will exit the loop when state reaches HWCNT_JM_WD_INIT_START*/
while (state-- > HWCNT_JM_WD_INIT_START) {
switch (state) {
case HWCNT_JM_WD_INIT_ALLOC:
kfree(wd_backend);
break;
case HWCNT_JM_WD_INIT_BACKEND:
wd_backend->info->jm_backend_iface->term(wd_backend->jm_backend);
break;
case HWCNT_JM_WD_INIT_ENABLE_MAP:
kbase_hwcnt_enable_map_free(&wd_backend->wd_enable_map);
break;
case HWCNT_JM_WD_INIT_DUMP_BUFFER:
kbase_hwcnt_dump_buffer_free(&wd_backend->wd_dump_buffer);
break;
case HWCNT_JM_WD_INIT_END:
break;
}
}
}
/* Job manager watchdog backend, implementation of kbase_hwcnt_backend_term_fn
* Calling term does *not* destroy the interface
*/
static void kbasep_hwcnt_backend_jm_watchdog_term(struct kbase_hwcnt_backend *backend)
{
if (!backend)
return;
kbasep_hwcnt_backend_jm_watchdog_term_partial(
(struct kbase_hwcnt_backend_jm_watchdog *)backend, HWCNT_JM_WD_INIT_END);
}
/* Job manager watchdog backend, implementation of kbase_hwcnt_backend_init_fn */
static int kbasep_hwcnt_backend_jm_watchdog_init(const struct kbase_hwcnt_backend_info *info,
struct kbase_hwcnt_backend **out_backend)
{
int errcode = 0;
struct kbase_hwcnt_backend_jm_watchdog *wd_backend = NULL;
struct kbase_hwcnt_backend_jm_watchdog_info *const wd_info = (void *)info;
const struct kbase_hwcnt_backend_info *jm_info;
const struct kbase_hwcnt_metadata *metadata;
enum wd_init_state state = HWCNT_JM_WD_INIT_START;
if (WARN_ON(!info) || WARN_ON(!out_backend))
return -EINVAL;
jm_info = wd_info->jm_backend_iface->info;
metadata = wd_info->jm_backend_iface->metadata(wd_info->jm_backend_iface->info);
while (state < HWCNT_JM_WD_INIT_END && !errcode) {
switch (state) {
case HWCNT_JM_WD_INIT_ALLOC:
wd_backend = kmalloc(sizeof(*wd_backend), GFP_KERNEL);
if (wd_backend) {
*wd_backend = (struct kbase_hwcnt_backend_jm_watchdog){
.info = wd_info,
.timeout_ms = hwcnt_backend_watchdog_timer_interval_ms,
.locked = { .state = HWCNT_JM_WD_IDLE_BUFFER_EMPTY,
.is_enabled = false }
};
} else
errcode = -ENOMEM;
break;
case HWCNT_JM_WD_INIT_BACKEND:
errcode = wd_info->jm_backend_iface->init(jm_info, &wd_backend->jm_backend);
break;
case HWCNT_JM_WD_INIT_ENABLE_MAP:
errcode =
kbase_hwcnt_enable_map_alloc(metadata, &wd_backend->wd_enable_map);
break;
case HWCNT_JM_WD_INIT_DUMP_BUFFER:
errcode = kbase_hwcnt_dump_buffer_alloc(metadata,
&wd_backend->wd_dump_buffer);
break;
case HWCNT_JM_WD_INIT_END:
break;
}
if (!errcode)
state++;
}
if (errcode) {
kbasep_hwcnt_backend_jm_watchdog_term_partial(wd_backend, state);
*out_backend = NULL;
return errcode;
}
WARN_ON(state != HWCNT_JM_WD_INIT_END);
spin_lock_init(&wd_backend->locked.watchdog_lock);
init_completion(&wd_backend->watchdog_complete);
*out_backend = (struct kbase_hwcnt_backend *)wd_backend;
return 0;
}
/* Job manager watchdog backend, implementation of timestamp_ns */
static u64 kbasep_hwcnt_backend_jm_watchdog_timestamp_ns(struct kbase_hwcnt_backend *backend)
{
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
return wd_backend->info->jm_backend_iface->timestamp_ns(wd_backend->jm_backend);
}
static int kbasep_hwcnt_backend_jm_watchdog_dump_enable_common(
struct kbase_hwcnt_backend_jm_watchdog *wd_backend,
const struct kbase_hwcnt_enable_map *enable_map, kbase_hwcnt_backend_dump_enable_fn enabler)
{
int errcode = -EPERM;
unsigned long flags;
if (WARN_ON(!wd_backend) || WARN_ON(!enable_map))
return -EINVAL;
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
/* If the backend is already enabled return an error */
if (wd_backend->locked.is_enabled) {
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
return -EPERM;
}
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
/*We copy the enable map into our watchdog backend copy, for future usage*/
kbase_hwcnt_enable_map_copy(&wd_backend->wd_enable_map, enable_map);
errcode = enabler(wd_backend->jm_backend, enable_map);
if (!errcode) {
/*Enable dump watchdog*/
errcode = wd_backend->info->dump_watchdog_iface->enable(
wd_backend->info->dump_watchdog_iface->timer, wd_backend->timeout_ms,
kbasep_hwcnt_backend_jm_watchdog_timer_callback, wd_backend);
if (!errcode) {
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
WARN_ON(wd_backend->locked.is_enabled);
wd_backend->locked.is_enabled = true;
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
} else
/*Reverting the job manager backend back to disabled*/
wd_backend->info->jm_backend_iface->dump_disable(wd_backend->jm_backend);
}
return errcode;
}
/* Job manager watchdog backend, implementation of dump_enable */
static int
kbasep_hwcnt_backend_jm_watchdog_dump_enable(struct kbase_hwcnt_backend *backend,
const struct kbase_hwcnt_enable_map *enable_map)
{
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
return kbasep_hwcnt_backend_jm_watchdog_dump_enable_common(
wd_backend, enable_map, wd_backend->info->jm_backend_iface->dump_enable);
}
/* Job manager watchdog backend, implementation of dump_enable_nolock */
static int
kbasep_hwcnt_backend_jm_watchdog_dump_enable_nolock(struct kbase_hwcnt_backend *backend,
const struct kbase_hwcnt_enable_map *enable_map)
{
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
return kbasep_hwcnt_backend_jm_watchdog_dump_enable_common(
wd_backend, enable_map, wd_backend->info->jm_backend_iface->dump_enable_nolock);
}
/* Job manager watchdog backend, implementation of dump_disable */
static void kbasep_hwcnt_backend_jm_watchdog_dump_disable(struct kbase_hwcnt_backend *backend)
{
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
unsigned long flags;
if (WARN_ON(!backend))
return;
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
if (!wd_backend->locked.is_enabled) {
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
return;
}
wd_backend->locked.is_enabled = false;
/* Discard undumped counter values since the last dump_get. */
if (wd_backend->locked.state == HWCNT_JM_WD_IDLE_BUFFER_FULL)
wd_backend->locked.state = HWCNT_JM_WD_IDLE_BUFFER_EMPTY;
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
wd_backend->info->dump_watchdog_iface->disable(
wd_backend->info->dump_watchdog_iface->timer);
wd_backend->info->jm_backend_iface->dump_disable(wd_backend->jm_backend);
}
/* Job manager watchdog backend, implementation of dump_clear */
static int kbasep_hwcnt_backend_jm_watchdog_dump_clear(struct kbase_hwcnt_backend *backend)
{
int errcode = -EPERM;
bool clear_wd_wait_completion = false;
unsigned long flags;
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
if (WARN_ON(!backend))
return -EINVAL;
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
if (!wd_backend->locked.is_enabled) {
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
return -EPERM;
}
switch (wd_backend->locked.state) {
case HWCNT_JM_WD_IDLE_BUFFER_FULL:
case HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL:
case HWCNT_JM_WD_IDLE_BUFFER_EMPTY:
case HWCNT_JM_WD_USER_DUMPING_BUFFER_EMPTY:
wd_backend->locked.state = HWCNT_JM_WD_BUFFER_CLEARING;
errcode = 0;
break;
case HWCNT_JM_WD_TIMER_DUMPING:
/* The timer asked for a dump request, when complete, the job manager backend
* buffer will be zero
*/
clear_wd_wait_completion = true;
/* This thread will have to wait for the callback to terminate and then call a
* dump_clear on the job manager backend. We change the state to
* HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR to notify the callback thread there is
* no more need to dump the buffer (since we will clear it right after anyway).
* We set up a wait queue to synchronize with the callback.
*/
reinit_completion(&wd_backend->watchdog_complete);
wd_backend->locked.state = HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR;
errcode = 0;
break;
default:
errcode = -EPERM;
break;
}
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
if (!errcode) {
if (clear_wd_wait_completion) {
/* Waiting for the callback to finish */
wait_for_completion(&wd_backend->watchdog_complete);
}
/* Clearing job manager backend buffer */
errcode = wd_backend->info->jm_backend_iface->dump_clear(wd_backend->jm_backend);
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
WARN_ON(wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING_USER_CLEAR &&
wd_backend->locked.state != HWCNT_JM_WD_BUFFER_CLEARING &&
wd_backend->locked.state != HWCNT_JM_WD_ERROR);
WARN_ON(!wd_backend->locked.is_enabled);
if (!errcode && wd_backend->locked.state != HWCNT_JM_WD_ERROR) {
/* Setting the internal buffer state to EMPTY */
wd_backend->locked.state = HWCNT_JM_WD_IDLE_BUFFER_EMPTY;
/* Resetting the timer. Calling modify on a disabled timer
* enables it.
*/
wd_backend->info->dump_watchdog_iface->modify(
wd_backend->info->dump_watchdog_iface->timer,
wd_backend->timeout_ms);
} else {
wd_backend->locked.state = HWCNT_JM_WD_ERROR;
errcode = -EPERM;
}
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
}
return errcode;
}
/* Job manager watchdog backend, implementation of dump_request */
static int kbasep_hwcnt_backend_jm_watchdog_dump_request(struct kbase_hwcnt_backend *backend,
u64 *dump_time_ns)
{
bool call_dump_request = false;
int errcode = 0;
unsigned long flags;
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
if (WARN_ON(!backend) || WARN_ON(!dump_time_ns))
return -EINVAL;
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
if (!wd_backend->locked.is_enabled) {
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
return -EPERM;
}
switch (wd_backend->locked.state) {
case HWCNT_JM_WD_IDLE_BUFFER_EMPTY:
/* progressing the state to avoid callbacks running while calling the job manager
* backend
*/
wd_backend->locked.state = HWCNT_JM_WD_USER_DUMPING_BUFFER_EMPTY;
call_dump_request = true;
break;
case HWCNT_JM_WD_IDLE_BUFFER_FULL:
wd_backend->locked.state = HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL;
call_dump_request = true;
break;
case HWCNT_JM_WD_TIMER_DUMPING:
/* Retrieve timing information from previous dump_request */
*dump_time_ns = wd_backend->wd_dump_timestamp;
/* On the next client call (dump_wait) the thread will have to wait for the
* callback to finish the dumping.
* We set up a wait queue to synchronize with the callback.
*/
reinit_completion(&wd_backend->watchdog_complete);
wd_backend->locked.state = HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED;
break;
default:
errcode = -EPERM;
break;
}
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
if (call_dump_request) {
errcode = wd_backend->info->jm_backend_iface->dump_request(wd_backend->jm_backend,
dump_time_ns);
if (!errcode) {
/*resetting the timer. Calling modify on a disabled timer enables it*/
wd_backend->info->dump_watchdog_iface->modify(
wd_backend->info->dump_watchdog_iface->timer,
wd_backend->timeout_ms);
} else {
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
WARN_ON(!wd_backend->locked.is_enabled);
wd_backend->locked.state = HWCNT_JM_WD_ERROR;
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
}
}
return errcode;
}
/* Job manager watchdog backend, implementation of dump_wait */
static int kbasep_hwcnt_backend_jm_watchdog_dump_wait(struct kbase_hwcnt_backend *backend)
{
int errcode = -EPERM;
bool wait_for_auto_dump = false, wait_for_user_dump = false;
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
unsigned long flags;
if (WARN_ON(!backend))
return -EINVAL;
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
if (!wd_backend->locked.is_enabled) {
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
return -EPERM;
}
switch (wd_backend->locked.state) {
case HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED:
wait_for_auto_dump = true;
errcode = 0;
break;
case HWCNT_JM_WD_USER_DUMPING_BUFFER_EMPTY:
case HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL:
wait_for_user_dump = true;
errcode = 0;
break;
default:
errcode = -EPERM;
break;
}
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
if (wait_for_auto_dump)
wait_for_completion(&wd_backend->watchdog_complete);
else if (wait_for_user_dump) {
errcode = wd_backend->info->jm_backend_iface->dump_wait(wd_backend->jm_backend);
if (errcode) {
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
WARN_ON(!wd_backend->locked.is_enabled);
wd_backend->locked.state = HWCNT_JM_WD_ERROR;
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
}
}
return errcode;
}
/* Job manager watchdog backend, implementation of dump_get */
static int kbasep_hwcnt_backend_jm_watchdog_dump_get(
struct kbase_hwcnt_backend *backend, struct kbase_hwcnt_dump_buffer *dump_buffer,
const struct kbase_hwcnt_enable_map *enable_map, bool accumulate)
{
bool call_dump_get = false;
struct kbase_hwcnt_backend_jm_watchdog *const wd_backend = (void *)backend;
unsigned long flags;
int errcode = 0;
if (WARN_ON(!backend) || WARN_ON(!dump_buffer) || WARN_ON(!enable_map))
return -EINVAL;
/* The resultant contents of the dump buffer are only well defined if a prior
* call to dump_wait returned successfully, and a new dump has not yet been
* requested by a call to dump_request.
*/
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
switch (wd_backend->locked.state) {
case HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED:
/*we assume dump_wait has been called and completed successfully*/
if (accumulate)
kbase_hwcnt_dump_buffer_accumulate(dump_buffer, &wd_backend->wd_dump_buffer,
enable_map);
else
kbase_hwcnt_dump_buffer_copy(dump_buffer, &wd_backend->wd_dump_buffer,
enable_map);
/*use state to indicate the the buffer is now empty*/
wd_backend->locked.state = HWCNT_JM_WD_IDLE_BUFFER_EMPTY;
break;
case HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL:
/*accumulate or copy watchdog data to user buffer first so that dump_get can set
* the header correctly
*/
if (accumulate)
kbase_hwcnt_dump_buffer_accumulate(dump_buffer, &wd_backend->wd_dump_buffer,
enable_map);
else
kbase_hwcnt_dump_buffer_copy(dump_buffer, &wd_backend->wd_dump_buffer,
enable_map);
/*accumulate backend data into user buffer on top of watchdog data*/
accumulate = true;
call_dump_get = true;
break;
case HWCNT_JM_WD_USER_DUMPING_BUFFER_EMPTY:
call_dump_get = true;
break;
default:
errcode = -EPERM;
break;
}
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
if (call_dump_get && !errcode) {
/*we just dump the job manager backend into the user buffer, following
*accumulate flag
*/
errcode = wd_backend->info->jm_backend_iface->dump_get(
wd_backend->jm_backend, dump_buffer, enable_map, accumulate);
spin_lock_irqsave(&wd_backend->locked.watchdog_lock, flags);
WARN_ON(wd_backend->locked.state != HWCNT_JM_WD_USER_DUMPING_BUFFER_EMPTY &&
wd_backend->locked.state != HWCNT_JM_WD_USER_DUMPING_BUFFER_FULL &&
wd_backend->locked.state != HWCNT_JM_WD_TIMER_DUMPING_USER_REQUESTED);
if (!errcode)
wd_backend->locked.state = HWCNT_JM_WD_IDLE_BUFFER_EMPTY;
else
wd_backend->locked.state = HWCNT_JM_WD_ERROR;
spin_unlock_irqrestore(&wd_backend->locked.watchdog_lock, flags);
}
return errcode;
}
/* exposed methods */
int kbase_hwcnt_backend_jm_watchdog_create(struct kbase_hwcnt_backend_interface *backend_iface,
struct kbase_hwcnt_watchdog_interface *watchdog_iface,
struct kbase_hwcnt_backend_interface *out_iface)
{
struct kbase_hwcnt_backend_jm_watchdog_info *info = NULL;
if (WARN_ON(!backend_iface) || WARN_ON(!watchdog_iface) || WARN_ON(!out_iface))
return -EINVAL;
info = kbasep_hwcnt_backend_jm_watchdog_info_create(backend_iface, watchdog_iface);
if (!info)
return -ENOMEM;
/*linking the info table with the output iface, to allow the callbacks below to access the
*info object later on
*/
*out_iface = (struct kbase_hwcnt_backend_interface){
.info = (void *)info,
.metadata = kbasep_hwcnt_backend_jm_watchdog_metadata,
.init = kbasep_hwcnt_backend_jm_watchdog_init,
.term = kbasep_hwcnt_backend_jm_watchdog_term,
.timestamp_ns = kbasep_hwcnt_backend_jm_watchdog_timestamp_ns,
.dump_enable = kbasep_hwcnt_backend_jm_watchdog_dump_enable,
.dump_enable_nolock = kbasep_hwcnt_backend_jm_watchdog_dump_enable_nolock,
.dump_disable = kbasep_hwcnt_backend_jm_watchdog_dump_disable,
.dump_clear = kbasep_hwcnt_backend_jm_watchdog_dump_clear,
.dump_request = kbasep_hwcnt_backend_jm_watchdog_dump_request,
.dump_wait = kbasep_hwcnt_backend_jm_watchdog_dump_wait,
.dump_get = kbasep_hwcnt_backend_jm_watchdog_dump_get
};
/*registering watchdog backend module methods on the output interface*/
return 0;
}
void kbase_hwcnt_backend_jm_watchdog_destroy(struct kbase_hwcnt_backend_interface *iface)
{
if (!iface || !iface->info)
return;
kfree((struct kbase_hwcnt_backend_jm_watchdog_info *)iface->info);
/*blanking the watchdog backend interface*/
*iface = (struct kbase_hwcnt_backend_interface){ NULL };
}