diff options
Diffstat (limited to 'mali_kbase/hwcnt/backend/mali_kbase_hwcnt_backend_jm_watchdog.c')
-rw-r--r-- | mali_kbase/hwcnt/backend/mali_kbase_hwcnt_backend_jm_watchdog.c | 829 |
1 files changed, 829 insertions, 0 deletions
diff --git a/mali_kbase/hwcnt/backend/mali_kbase_hwcnt_backend_jm_watchdog.c b/mali_kbase/hwcnt/backend/mali_kbase_hwcnt_backend_jm_watchdog.c new file mode 100644 index 0000000..a8654ea --- /dev/null +++ b/mali_kbase/hwcnt/backend/mali_kbase_hwcnt_backend_jm_watchdog.c @@ -0,0 +1,829 @@ +// 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 <hwcnt/mali_kbase_hwcnt_gpu.h> +#include <hwcnt/mali_kbase_hwcnt_types.h> + +#include <hwcnt/backend/mali_kbase_hwcnt_backend.h> +#include <hwcnt/backend/mali_kbase_hwcnt_backend_jm_watchdog.h> +#include <hwcnt/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_BACKEND = HWCNT_JM_WD_INIT_START, + 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; + + WARN_ON(state > HWCNT_JM_WD_INIT_END); + + while (state-- > HWCNT_JM_WD_INIT_START) { + switch (state) { + 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; + } + } + + kfree(wd_backend); +} + +/* 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) +{ + struct kbase_hwcnt_backend_jm_watchdog *wd_backend = + (struct kbase_hwcnt_backend_jm_watchdog *)backend; + + if (!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); + + kbasep_hwcnt_backend_jm_watchdog_term_partial(wd_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); + + wd_backend = kmalloc(sizeof(*wd_backend), GFP_KERNEL); + if (!wd_backend) { + *out_backend = NULL; + return -ENOMEM; + } + + *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 } + }; + + while (state < HWCNT_JM_WD_INIT_END && !errcode) { + switch (state) { + 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*/ + memset(iface, 0, sizeof(*iface)); +} |