| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 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> |
| |
| #if IS_ENABLED(CONFIG_DEBUG_FS) |
| |
| /** |
| * kbasep_fault_occurred - Check if fault occurred. |
| * |
| * @kbdev: Device pointer |
| * |
| * Return: true if a fault occurred. |
| */ |
| static bool kbasep_fault_occurred(struct kbase_device *kbdev) |
| { |
| unsigned long flags; |
| bool ret; |
| |
| spin_lock_irqsave(&kbdev->csf.dof.lock, flags); |
| ret = (kbdev->csf.dof.error_code != DF_NO_ERROR); |
| spin_unlock_irqrestore(&kbdev->csf.dof.lock, flags); |
| |
| return ret; |
| } |
| |
| void kbase_debug_csf_fault_wait_completion(struct kbase_device *kbdev) |
| { |
| if (likely(!kbase_debug_csf_fault_dump_enabled(kbdev))) { |
| dev_dbg(kbdev->dev, "No userspace client for dumping exists"); |
| return; |
| } |
| |
| wait_event(kbdev->csf.dof.dump_wait_wq, kbase_debug_csf_fault_dump_complete(kbdev)); |
| } |
| KBASE_EXPORT_TEST_API(kbase_debug_csf_fault_wait_completion); |
| |
| /** |
| * kbase_debug_csf_fault_wakeup - Wake up a waiting user space client. |
| * |
| * @kbdev: Kbase device |
| */ |
| static void kbase_debug_csf_fault_wakeup(struct kbase_device *kbdev) |
| { |
| wake_up_interruptible(&kbdev->csf.dof.fault_wait_wq); |
| } |
| |
| bool kbase_debug_csf_fault_notify(struct kbase_device *kbdev, |
| struct kbase_context *kctx, enum dumpfault_error_type error) |
| { |
| unsigned long flags; |
| |
| if (likely(!kbase_debug_csf_fault_dump_enabled(kbdev))) |
| return false; |
| |
| if (WARN_ON(error == DF_NO_ERROR)) |
| return false; |
| |
| if (kctx && kbase_ctx_flag(kctx, KCTX_DYING)) { |
| dev_info(kbdev->dev, "kctx %d_%d is dying when error %d is reported", |
| kctx->tgid, kctx->id, error); |
| kctx = NULL; |
| } |
| |
| spin_lock_irqsave(&kbdev->csf.dof.lock, flags); |
| |
| /* Only one fault at a time can be processed */ |
| if (kbdev->csf.dof.error_code) { |
| dev_info(kbdev->dev, "skip this fault as there's a pending fault"); |
| goto unlock; |
| } |
| |
| kbdev->csf.dof.kctx_tgid = kctx ? kctx->tgid : 0; |
| kbdev->csf.dof.kctx_id = kctx ? kctx->id : 0; |
| kbdev->csf.dof.error_code = error; |
| kbase_debug_csf_fault_wakeup(kbdev); |
| |
| unlock: |
| spin_unlock_irqrestore(&kbdev->csf.dof.lock, flags); |
| return true; |
| } |
| |
| static ssize_t debug_csf_fault_read(struct file *file, char __user *buffer, size_t size, |
| loff_t *f_pos) |
| { |
| #define BUF_SIZE 64 |
| struct kbase_device *kbdev; |
| unsigned long flags; |
| int count; |
| char buf[BUF_SIZE]; |
| u32 tgid, ctx_id; |
| enum dumpfault_error_type error_code; |
| |
| if (unlikely(!file)) { |
| pr_warn("%s: file is NULL", __func__); |
| return -EINVAL; |
| } |
| |
| kbdev = file->private_data; |
| if (unlikely(!buffer)) { |
| dev_warn(kbdev->dev, "%s: buffer is NULL", __func__); |
| return -EINVAL; |
| } |
| |
| if (unlikely(*f_pos < 0)) { |
| dev_warn(kbdev->dev, "%s: f_pos is negative", __func__); |
| return -EINVAL; |
| } |
| |
| if (size < sizeof(buf)) { |
| dev_warn(kbdev->dev, "%s: buffer is too small", __func__); |
| return -EINVAL; |
| } |
| |
| if (wait_event_interruptible(kbdev->csf.dof.fault_wait_wq, kbasep_fault_occurred(kbdev))) |
| return -ERESTARTSYS; |
| |
| spin_lock_irqsave(&kbdev->csf.dof.lock, flags); |
| tgid = kbdev->csf.dof.kctx_tgid; |
| ctx_id = kbdev->csf.dof.kctx_id; |
| error_code = kbdev->csf.dof.error_code; |
| BUILD_BUG_ON(sizeof(buf) < (sizeof(tgid) + sizeof(ctx_id) + sizeof(error_code))); |
| count = scnprintf(buf, sizeof(buf), "%u_%u_%u\n", tgid, ctx_id, error_code); |
| spin_unlock_irqrestore(&kbdev->csf.dof.lock, flags); |
| |
| dev_info(kbdev->dev, "debug csf fault info read"); |
| return simple_read_from_buffer(buffer, size, f_pos, buf, count); |
| } |
| |
| static int debug_csf_fault_open(struct inode *in, struct file *file) |
| { |
| struct kbase_device *kbdev; |
| |
| if (unlikely(!in)) { |
| pr_warn("%s: inode is NULL", __func__); |
| return -EINVAL; |
| } |
| |
| kbdev = in->i_private; |
| if (unlikely(!file)) { |
| dev_warn(kbdev->dev, "%s: file is NULL", __func__); |
| return -EINVAL; |
| } |
| |
| if (atomic_cmpxchg(&kbdev->csf.dof.enabled, 0, 1) == 1) { |
| dev_warn(kbdev->dev, "Only one client is allowed for dump on fault"); |
| return -EBUSY; |
| } |
| |
| dev_info(kbdev->dev, "debug csf fault file open"); |
| |
| return simple_open(in, file); |
| } |
| |
| static ssize_t debug_csf_fault_write(struct file *file, const char __user *ubuf, size_t count, |
| loff_t *ppos) |
| { |
| struct kbase_device *kbdev; |
| unsigned long flags; |
| |
| if (unlikely(!file)) { |
| pr_warn("%s: file is NULL", __func__); |
| return -EINVAL; |
| } |
| |
| kbdev = file->private_data; |
| spin_lock_irqsave(&kbdev->csf.dof.lock, flags); |
| kbdev->csf.dof.error_code = DF_NO_ERROR; |
| kbdev->csf.dof.kctx_tgid = 0; |
| kbdev->csf.dof.kctx_id = 0; |
| dev_info(kbdev->dev, "debug csf fault dump complete"); |
| spin_unlock_irqrestore(&kbdev->csf.dof.lock, flags); |
| |
| /* User space finished the dump. |
| * Wake up blocked kernel threads to proceed. |
| */ |
| wake_up(&kbdev->csf.dof.dump_wait_wq); |
| |
| return count; |
| } |
| |
| static int debug_csf_fault_release(struct inode *in, struct file *file) |
| { |
| struct kbase_device *kbdev; |
| unsigned long flags; |
| |
| if (unlikely(!in)) { |
| pr_warn("%s: inode is NULL", __func__); |
| return -EINVAL; |
| } |
| |
| kbdev = in->i_private; |
| spin_lock_irqsave(&kbdev->csf.dof.lock, flags); |
| kbdev->csf.dof.kctx_tgid = 0; |
| kbdev->csf.dof.kctx_id = 0; |
| kbdev->csf.dof.error_code = DF_NO_ERROR; |
| spin_unlock_irqrestore(&kbdev->csf.dof.lock, flags); |
| |
| atomic_set(&kbdev->csf.dof.enabled, 0); |
| dev_info(kbdev->dev, "debug csf fault file close"); |
| |
| /* User space closed the debugfs file. |
| * Wake up blocked kernel threads to resume. |
| */ |
| wake_up(&kbdev->csf.dof.dump_wait_wq); |
| |
| return 0; |
| } |
| |
| static const struct file_operations kbasep_debug_csf_fault_fops = { |
| .owner = THIS_MODULE, |
| .open = debug_csf_fault_open, |
| .read = debug_csf_fault_read, |
| .write = debug_csf_fault_write, |
| .llseek = default_llseek, |
| .release = debug_csf_fault_release, |
| }; |
| |
| void kbase_debug_csf_fault_debugfs_init(struct kbase_device *kbdev) |
| { |
| const char *fname = "csf_fault"; |
| |
| if (unlikely(!kbdev)) { |
| pr_warn("%s: kbdev is NULL", __func__); |
| return; |
| } |
| |
| debugfs_create_file(fname, 0600, kbdev->mali_debugfs_directory, kbdev, |
| &kbasep_debug_csf_fault_fops); |
| } |
| |
| int kbase_debug_csf_fault_init(struct kbase_device *kbdev) |
| { |
| if (unlikely(!kbdev)) { |
| pr_warn("%s: kbdev is NULL", __func__); |
| return -EINVAL; |
| } |
| |
| init_waitqueue_head(&(kbdev->csf.dof.fault_wait_wq)); |
| init_waitqueue_head(&(kbdev->csf.dof.dump_wait_wq)); |
| spin_lock_init(&kbdev->csf.dof.lock); |
| kbdev->csf.dof.kctx_tgid = 0; |
| kbdev->csf.dof.kctx_id = 0; |
| kbdev->csf.dof.error_code = DF_NO_ERROR; |
| atomic_set(&kbdev->csf.dof.enabled, 0); |
| |
| return 0; |
| } |
| |
| void kbase_debug_csf_fault_term(struct kbase_device *kbdev) |
| { |
| } |
| #endif /* CONFIG_DEBUG_FS */ |