| // 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_watchdog_if.h" |
| #include "hwcnt/mali_kbase_hwcnt_watchdog_if_timer.h" |
| |
| #include <linux/workqueue.h> |
| #include <linux/slab.h> |
| |
| /** |
| * struct kbase_hwcnt_watchdog_if_timer_info - Timer information for watchdog |
| * interface. |
| * |
| * @workq: Single threaded work queue in which to execute callbacks. |
| * @dwork: Worker to execute callback function. |
| * @timer_enabled: True if watchdog timer enabled, otherwise false |
| * @callback: Watchdog callback function |
| * @user_data: Pointer to user data passed as argument to the callback |
| * function |
| */ |
| struct kbase_hwcnt_watchdog_if_timer_info { |
| struct workqueue_struct *workq; |
| struct delayed_work dwork; |
| bool timer_enabled; |
| kbase_hwcnt_watchdog_callback_fn *callback; |
| void *user_data; |
| }; |
| |
| /** |
| * kbasep_hwcnt_watchdog_callback() - Watchdog callback |
| * |
| * @work: Work structure |
| * |
| * Function to be called in a work queue after watchdog timer has expired. |
| */ |
| static void kbasep_hwcnt_watchdog_callback(struct work_struct *const work) |
| { |
| struct kbase_hwcnt_watchdog_if_timer_info *const info = |
| container_of(work, struct kbase_hwcnt_watchdog_if_timer_info, dwork.work); |
| |
| if (info->callback) |
| info->callback(info->user_data); |
| } |
| |
| static int kbasep_hwcnt_watchdog_if_timer_enable( |
| const struct kbase_hwcnt_watchdog_info *const timer, u32 const period_ms, |
| kbase_hwcnt_watchdog_callback_fn *const callback, void *const user_data) |
| { |
| struct kbase_hwcnt_watchdog_if_timer_info *const timer_info = (void *)timer; |
| |
| if (WARN_ON(!timer) || WARN_ON(!callback) || WARN_ON(timer_info->timer_enabled)) |
| return -EINVAL; |
| |
| timer_info->callback = callback; |
| timer_info->user_data = user_data; |
| |
| queue_delayed_work(timer_info->workq, &timer_info->dwork, msecs_to_jiffies(period_ms)); |
| timer_info->timer_enabled = true; |
| |
| return 0; |
| } |
| |
| static void |
| kbasep_hwcnt_watchdog_if_timer_disable(const struct kbase_hwcnt_watchdog_info *const timer) |
| { |
| struct kbase_hwcnt_watchdog_if_timer_info *const timer_info = (void *)timer; |
| |
| if (WARN_ON(!timer)) |
| return; |
| |
| if (!timer_info->timer_enabled) |
| return; |
| |
| cancel_delayed_work_sync(&timer_info->dwork); |
| timer_info->timer_enabled = false; |
| } |
| |
| static void |
| kbasep_hwcnt_watchdog_if_timer_modify(const struct kbase_hwcnt_watchdog_info *const timer, |
| u32 const delay_ms) |
| { |
| struct kbase_hwcnt_watchdog_if_timer_info *const timer_info = (void *)timer; |
| |
| if (WARN_ON(!timer) || WARN_ON(!timer_info->timer_enabled)) |
| return; |
| |
| mod_delayed_work(timer_info->workq, &timer_info->dwork, msecs_to_jiffies(delay_ms)); |
| } |
| |
| void kbase_hwcnt_watchdog_if_timer_destroy(struct kbase_hwcnt_watchdog_interface *const watchdog_if) |
| { |
| struct kbase_hwcnt_watchdog_if_timer_info *timer_info; |
| |
| if (WARN_ON(!watchdog_if)) |
| return; |
| |
| timer_info = (void *)watchdog_if->timer; |
| |
| if (WARN_ON(!timer_info)) |
| return; |
| |
| destroy_workqueue(timer_info->workq); |
| kfree(timer_info); |
| |
| *watchdog_if = (struct kbase_hwcnt_watchdog_interface){ |
| .timer = NULL, .enable = NULL, .disable = NULL, .modify = NULL |
| }; |
| } |
| |
| int kbase_hwcnt_watchdog_if_timer_create(struct kbase_hwcnt_watchdog_interface *const watchdog_if) |
| { |
| struct kbase_hwcnt_watchdog_if_timer_info *timer_info; |
| |
| if (WARN_ON(!watchdog_if)) |
| return -EINVAL; |
| |
| timer_info = kmalloc(sizeof(*timer_info), GFP_KERNEL); |
| if (!timer_info) |
| return -ENOMEM; |
| |
| *timer_info = (struct kbase_hwcnt_watchdog_if_timer_info){ .timer_enabled = false }; |
| |
| INIT_DELAYED_WORK(&timer_info->dwork, kbasep_hwcnt_watchdog_callback); |
| |
| *watchdog_if = (struct kbase_hwcnt_watchdog_interface){ |
| .timer = (void *)timer_info, |
| .enable = kbasep_hwcnt_watchdog_if_timer_enable, |
| .disable = kbasep_hwcnt_watchdog_if_timer_disable, |
| .modify = kbasep_hwcnt_watchdog_if_timer_modify, |
| }; |
| |
| timer_info->workq = alloc_workqueue("mali_hwc_watchdog_wq", WQ_HIGHPRI | WQ_UNBOUND, 1); |
| if (timer_info->workq) |
| return 0; |
| |
| kfree(timer_info); |
| return -ENOMEM; |
| } |