// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note /* * * (C) COPYRIGHT 2021-2023 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 #include "mali_kbase_csf_event.h" /** * struct kbase_csf_event_cb - CSF event callback. * * @link: Link to the rest of the list. * @kctx: Pointer to the Kbase context this event belongs to. * @callback: Callback function to call when a CSF event is signalled. * @param: Parameter to pass to the callback function. * * This structure belongs to the list of events which is part of a Kbase * context, and describes a callback function with a custom parameter to pass * to it when a CSF event is signalled. */ struct kbase_csf_event_cb { struct list_head link; struct kbase_context *kctx; kbase_csf_event_callback *callback; void *param; }; int kbase_csf_event_wait_add(struct kbase_context *kctx, kbase_csf_event_callback *callback, void *param) { int err = -ENOMEM; struct kbase_csf_event_cb *event_cb = kzalloc(sizeof(struct kbase_csf_event_cb), GFP_KERNEL); if (event_cb) { unsigned long flags; event_cb->kctx = kctx; event_cb->callback = callback; event_cb->param = param; spin_lock_irqsave(&kctx->csf.event.lock, flags); list_add_tail(&event_cb->link, &kctx->csf.event.callback_list); dev_dbg(kctx->kbdev->dev, "Added event handler %pK with param %pK\n", event_cb, event_cb->param); spin_unlock_irqrestore(&kctx->csf.event.lock, flags); err = 0; } return err; } void kbase_csf_event_wait_remove(struct kbase_context *kctx, kbase_csf_event_callback *callback, void *param) { struct kbase_csf_event_cb *event_cb; unsigned long flags; spin_lock_irqsave(&kctx->csf.event.lock, flags); list_for_each_entry(event_cb, &kctx->csf.event.callback_list, link) { if ((event_cb->callback == callback) && (event_cb->param == param)) { list_del(&event_cb->link); dev_dbg(kctx->kbdev->dev, "Removed event handler %pK with param %pK\n", event_cb, event_cb->param); kfree(event_cb); break; } } spin_unlock_irqrestore(&kctx->csf.event.lock, flags); } static void sync_update_notify_gpu(struct kbase_context *kctx) { bool can_notify_gpu; unsigned long flags; spin_lock_irqsave(&kctx->kbdev->hwaccess_lock, flags); can_notify_gpu = kctx->kbdev->pm.backend.gpu_powered; #ifdef KBASE_PM_RUNTIME if (kctx->kbdev->pm.backend.gpu_sleep_mode_active) can_notify_gpu = false; #endif if (can_notify_gpu) { kbase_csf_ring_doorbell(kctx->kbdev, CSF_KERNEL_DOORBELL_NR); KBASE_KTRACE_ADD(kctx->kbdev, CSF_SYNC_UPDATE_NOTIFY_GPU_EVENT, kctx, 0u); } spin_unlock_irqrestore(&kctx->kbdev->hwaccess_lock, flags); } void kbase_csf_event_signal(struct kbase_context *kctx, bool notify_gpu) { struct kbase_csf_event_cb *event_cb, *next_event_cb; unsigned long flags; dev_dbg(kctx->kbdev->dev, "Signal event (%s GPU notify) for context %pK\n", notify_gpu ? "with" : "without", (void *)kctx); /* First increment the signal count and wake up event thread. */ atomic_set(&kctx->event_count, 1); kbase_event_wakeup_nosync(kctx); /* Signal the CSF firmware. This is to ensure that pending command * stream synch object wait operations are re-evaluated. * Write to GLB_DOORBELL would suffice as spec says that all pending * synch object wait operations are re-evaluated on a write to any * CS_DOORBELL/GLB_DOORBELL register. */ if (notify_gpu) sync_update_notify_gpu(kctx); /* Now invoke the callbacks registered on backend side. * Allow item removal inside the loop, if requested by the callback. */ spin_lock_irqsave(&kctx->csf.event.lock, flags); list_for_each_entry_safe(event_cb, next_event_cb, &kctx->csf.event.callback_list, link) { enum kbase_csf_event_callback_action action; dev_dbg(kctx->kbdev->dev, "Calling event handler %pK with param %pK\n", (void *)event_cb, event_cb->param); action = event_cb->callback(event_cb->param); if (action == KBASE_CSF_EVENT_CALLBACK_REMOVE) { list_del(&event_cb->link); kfree(event_cb); } } spin_unlock_irqrestore(&kctx->csf.event.lock, flags); } void kbase_csf_event_term(struct kbase_context *kctx) { struct kbase_csf_event_cb *event_cb, *next_event_cb; unsigned long flags; spin_lock_irqsave(&kctx->csf.event.lock, flags); list_for_each_entry_safe(event_cb, next_event_cb, &kctx->csf.event.callback_list, link) { list_del(&event_cb->link); dev_warn(kctx->kbdev->dev, "Removed event handler %pK with param %pK\n", (void *)event_cb, event_cb->param); kfree(event_cb); } WARN(!list_empty(&kctx->csf.event.error_list), "Error list not empty for ctx %d_%d\n", kctx->tgid, kctx->id); spin_unlock_irqrestore(&kctx->csf.event.lock, flags); } void kbase_csf_event_init(struct kbase_context *const kctx) { INIT_LIST_HEAD(&kctx->csf.event.callback_list); INIT_LIST_HEAD(&kctx->csf.event.error_list); spin_lock_init(&kctx->csf.event.lock); } void kbase_csf_event_remove_error(struct kbase_context *kctx, struct kbase_csf_notification *error) { unsigned long flags; spin_lock_irqsave(&kctx->csf.event.lock, flags); list_del_init(&error->link); spin_unlock_irqrestore(&kctx->csf.event.lock, flags); } bool kbase_csf_event_read_error(struct kbase_context *kctx, struct base_csf_notification *event_data) { struct kbase_csf_notification *error_data = NULL; unsigned long flags; spin_lock_irqsave(&kctx->csf.event.lock, flags); if (likely(!list_empty(&kctx->csf.event.error_list))) { error_data = list_first_entry(&kctx->csf.event.error_list, struct kbase_csf_notification, link); list_del_init(&error_data->link); *event_data = error_data->data; dev_dbg(kctx->kbdev->dev, "Dequeued error %pK in context %pK\n", (void *)error_data, (void *)kctx); } spin_unlock_irqrestore(&kctx->csf.event.lock, flags); return !!error_data; } void kbase_csf_event_add_error(struct kbase_context *const kctx, struct kbase_csf_notification *const error, struct base_csf_notification const *const data) { unsigned long flags; if (WARN_ON(!kctx)) return; if (WARN_ON(!error)) return; if (WARN_ON(!data)) return; spin_lock_irqsave(&kctx->csf.event.lock, flags); if (list_empty(&error->link)) { error->data = *data; list_add_tail(&error->link, &kctx->csf.event.error_list); dev_dbg(kctx->kbdev->dev, "Added error %pK of type %d in context %pK\n", (void *)error, data->type, (void *)kctx); } else { dev_dbg(kctx->kbdev->dev, "Error %pK of type %d already pending in context %pK", (void *)error, error->data.type, (void *)kctx); } spin_unlock_irqrestore(&kctx->csf.event.lock, flags); } bool kbase_csf_event_error_pending(struct kbase_context *kctx) { bool error_pending = false; unsigned long flags; /* Withhold the error event if the dump on fault is ongoing. * This would prevent the Userspace from taking error recovery actions * (which can potentially affect the state that is being dumped). * Event handling thread would eventually notice the error event. */ if (unlikely(!kbase_debug_csf_fault_dump_complete(kctx->kbdev))) return false; spin_lock_irqsave(&kctx->csf.event.lock, flags); error_pending = !list_empty(&kctx->csf.event.error_list); dev_dbg(kctx->kbdev->dev, "%s error is pending in context %pK\n", error_pending ? "An" : "No", (void *)kctx); spin_unlock_irqrestore(&kctx->csf.event.lock, flags); return error_pending; }