summaryrefslogtreecommitdiff
path: root/mali_kbase/hwcnt/backend/mali_kbase_hwcnt_backend_jm_watchdog.c
diff options
context:
space:
mode:
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.c829
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));
+}