123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
- #include <linux/debugfs.h>
- #include <linux/rwlock.h>
- #include "kgsl_debugfs.h"
- #include "kgsl_device.h"
- #include "kgsl_eventlog.h"
- #include "kgsl_trace.h"
- /*
- * Define an kmem cache for the event structures since we allocate and free them
- * so frequently
- */
- static struct kmem_cache *events_cache;
- static inline void signal_event(struct kgsl_device *device,
- struct kgsl_event *event, int result)
- {
- list_del(&event->node);
- event->result = result;
- kthread_queue_work(device->events_worker, &event->work);
- }
- /**
- * _kgsl_event_worker() - Work handler for processing GPU event callbacks
- * @work: Pointer to the kthread_work for the event
- *
- * Each event callback has its own kthread_work struct and is run on a event specific
- * worker thread. This is the worker that queues up the event callback function.
- */
- static void _kgsl_event_worker(struct kthread_work *work)
- {
- struct kgsl_event *event = container_of(work, struct kgsl_event, work);
- int id = KGSL_CONTEXT_ID(event->context);
- trace_kgsl_fire_event(id, event->timestamp, event->result,
- jiffies - event->created, event->func);
- log_kgsl_fire_event(id, event->timestamp, event->result,
- jiffies - event->created);
- event->func(event->device, event->group, event->priv, event->result);
- kgsl_context_put(event->context);
- kmem_cache_free(events_cache, event);
- }
- /* return true if the group needs to be processed */
- static bool _do_process_group(unsigned int processed, unsigned int cur)
- {
- if (processed == cur)
- return false;
- /*
- * This ensures that the timestamp didn't slip back accidently, maybe
- * due to a memory barrier issue. This is highly unlikely but we've
- * been burned here in the past.
- */
- if ((cur < processed) && ((processed - cur) < KGSL_TIMESTAMP_WINDOW))
- return false;
- return true;
- }
- static void _process_event_group(struct kgsl_device *device,
- struct kgsl_event_group *group, bool flush)
- {
- struct kgsl_event *event, *tmp;
- unsigned int timestamp;
- struct kgsl_context *context;
- if (group == NULL)
- return;
- context = group->context;
- /*
- * Sanity check to be sure that we aren't racing with the context
- * getting destroyed
- */
- if (WARN_ON(context != NULL && !_kgsl_context_get(context)))
- return;
- spin_lock(&group->lock);
- group->readtimestamp(device, group->priv, KGSL_TIMESTAMP_RETIRED,
- ×tamp);
- if (!flush && !_do_process_group(group->processed, timestamp))
- goto out;
- list_for_each_entry_safe(event, tmp, &group->events, node) {
- if (timestamp_cmp(event->timestamp, timestamp) <= 0)
- signal_event(device, event, KGSL_EVENT_RETIRED);
- else if (flush)
- signal_event(device, event, KGSL_EVENT_CANCELLED);
- }
- group->processed = timestamp;
- out:
- spin_unlock(&group->lock);
- kgsl_context_put(context);
- }
- /**
- * kgsl_process_event_group() - Handle all the retired events in a group
- * @device: Pointer to a KGSL device
- * @group: Pointer to a GPU events group to process
- */
- void kgsl_process_event_group(struct kgsl_device *device,
- struct kgsl_event_group *group)
- {
- _process_event_group(device, group, false);
- }
- /**
- * kgsl_flush_event_group() - flush all the events in a group by retiring the
- * ones can be retired and cancelling the ones that are pending
- * @device: Pointer to a KGSL device
- * @group: Pointer to a GPU events group to process
- */
- void kgsl_flush_event_group(struct kgsl_device *device,
- struct kgsl_event_group *group)
- {
- _process_event_group(device, group, true);
- }
- /**
- * kgsl_cancel_events_timestamp() - Cancel pending events for a given timestamp
- * @device: Pointer to a KGSL device
- * @group: Ponter to the GPU event group that owns the event
- * @timestamp: Registered expiry timestamp for the event
- */
- void kgsl_cancel_events_timestamp(struct kgsl_device *device,
- struct kgsl_event_group *group, unsigned int timestamp)
- {
- struct kgsl_event *event, *tmp;
- spin_lock(&group->lock);
- list_for_each_entry_safe(event, tmp, &group->events, node) {
- if (timestamp_cmp(timestamp, event->timestamp) == 0)
- signal_event(device, event, KGSL_EVENT_CANCELLED);
- }
- spin_unlock(&group->lock);
- }
- /**
- * kgsl_cancel_events() - Cancel all pending events in the group
- * @device: Pointer to a KGSL device
- * @group: Pointer to a kgsl_events_group
- */
- void kgsl_cancel_events(struct kgsl_device *device,
- struct kgsl_event_group *group)
- {
- struct kgsl_event *event, *tmp;
- spin_lock(&group->lock);
- list_for_each_entry_safe(event, tmp, &group->events, node)
- signal_event(device, event, KGSL_EVENT_CANCELLED);
- spin_unlock(&group->lock);
- }
- /**
- * kgsl_cancel_event() - Cancel a specific event from a group
- * @device: Pointer to a KGSL device
- * @group: Pointer to the group that contains the events
- * @timestamp: Registered expiry timestamp for the event
- * @func: Registered callback for the function
- * @priv: Registered priv data for the function
- */
- void kgsl_cancel_event(struct kgsl_device *device,
- struct kgsl_event_group *group, unsigned int timestamp,
- kgsl_event_func func, void *priv)
- {
- struct kgsl_event *event, *tmp;
- spin_lock(&group->lock);
- list_for_each_entry_safe(event, tmp, &group->events, node) {
- if (timestamp == event->timestamp && func == event->func &&
- event->priv == priv) {
- signal_event(device, event, KGSL_EVENT_CANCELLED);
- break;
- }
- }
- spin_unlock(&group->lock);
- }
- /**
- * kgsl_event_pending() - Searches for an event in an event group
- * @device: Pointer to a KGSL device
- * @group: Pointer to the group that contains the events
- * @timestamp: Registered expiry timestamp for the event
- * @func: Registered callback for the function
- * @priv: Registered priv data for the function
- */
- bool kgsl_event_pending(struct kgsl_device *device,
- struct kgsl_event_group *group,
- unsigned int timestamp, kgsl_event_func func, void *priv)
- {
- struct kgsl_event *event;
- bool result = false;
- spin_lock(&group->lock);
- list_for_each_entry(event, &group->events, node) {
- if (timestamp == event->timestamp && func == event->func &&
- event->priv == priv) {
- result = true;
- break;
- }
- }
- spin_unlock(&group->lock);
- return result;
- }
- /**
- * kgsl_add_event() - Add a new GPU event to a group
- * @device: Pointer to a KGSL device
- * @group: Pointer to the group to add the event to
- * @timestamp: Timestamp that the event will expire on
- * @func: Callback function for the event
- * @priv: Private data to send to the callback function
- */
- int kgsl_add_event(struct kgsl_device *device, struct kgsl_event_group *group,
- unsigned int timestamp, kgsl_event_func func, void *priv)
- {
- unsigned int queued;
- struct kgsl_context *context = group->context;
- struct kgsl_event *event;
- unsigned int retired;
- if (!func)
- return -EINVAL;
- /*
- * If the caller is creating their own timestamps, let them schedule
- * events in the future. Otherwise only allow timestamps that have been
- * queued.
- */
- if (!context || !(context->flags & KGSL_CONTEXT_USER_GENERATED_TS)) {
- group->readtimestamp(device, group->priv, KGSL_TIMESTAMP_QUEUED,
- &queued);
- if (timestamp_cmp(timestamp, queued) > 0)
- return -EINVAL;
- }
- event = kmem_cache_alloc(events_cache, GFP_KERNEL);
- if (event == NULL)
- return -ENOMEM;
- /* Get a reference to the context while the event is active */
- if (context != NULL && !_kgsl_context_get(context)) {
- kmem_cache_free(events_cache, event);
- return -ENOENT;
- }
- event->device = device;
- event->context = context;
- event->timestamp = timestamp;
- event->priv = priv;
- event->func = func;
- event->created = jiffies;
- event->group = group;
- kthread_init_work(&event->work, _kgsl_event_worker);
- trace_kgsl_register_event(KGSL_CONTEXT_ID(context), timestamp, func);
- spin_lock(&group->lock);
- /*
- * Check to see if the requested timestamp has already retired. If so,
- * schedule the callback right away
- */
- group->readtimestamp(device, group->priv, KGSL_TIMESTAMP_RETIRED,
- &retired);
- if (timestamp_cmp(retired, timestamp) >= 0) {
- event->result = KGSL_EVENT_RETIRED;
- kthread_queue_work(device->events_worker, &event->work);
- spin_unlock(&group->lock);
- return 0;
- }
- /* Add the event to the group list */
- list_add_tail(&event->node, &group->events);
- spin_unlock(&group->lock);
- return 0;
- }
- void kgsl_process_event_groups(struct kgsl_device *device)
- {
- struct kgsl_event_group *group;
- read_lock(&device->event_groups_lock);
- list_for_each_entry(group, &device->event_groups, group)
- _process_event_group(device, group, false);
- read_unlock(&device->event_groups_lock);
- }
- void kgsl_del_event_group(struct kgsl_device *device,
- struct kgsl_event_group *group)
- {
- /* Check if the group is uninintalized */
- if (!group->context)
- return;
- /* Make sure that all the events have been deleted from the list */
- WARN_ON(!list_empty(&group->events));
- write_lock(&device->event_groups_lock);
- list_del(&group->group);
- write_unlock(&device->event_groups_lock);
- }
- void kgsl_add_event_group(struct kgsl_device *device,
- struct kgsl_event_group *group, struct kgsl_context *context,
- readtimestamp_func readtimestamp,
- void *priv, const char *fmt, ...)
- {
- va_list args;
- WARN_ON(readtimestamp == NULL);
- spin_lock_init(&group->lock);
- INIT_LIST_HEAD(&group->events);
- group->context = context;
- group->readtimestamp = readtimestamp;
- group->priv = priv;
- if (fmt) {
- va_start(args, fmt);
- vsnprintf(group->name, sizeof(group->name), fmt, args);
- va_end(args);
- }
- write_lock(&device->event_groups_lock);
- list_add_tail(&group->group, &device->event_groups);
- write_unlock(&device->event_groups_lock);
- }
- static void events_debugfs_print_group(struct seq_file *s,
- struct kgsl_event_group *group)
- {
- struct kgsl_event *event;
- unsigned int retired;
- spin_lock(&group->lock);
- seq_printf(s, "%s: last=%d\n", group->name, group->processed);
- list_for_each_entry(event, &group->events, node) {
- group->readtimestamp(event->device, group->priv,
- KGSL_TIMESTAMP_RETIRED, &retired);
- seq_printf(s, "\t%u:%u age=%lu func=%ps [retired=%u]\n",
- group->context ? group->context->id :
- KGSL_MEMSTORE_GLOBAL,
- event->timestamp, jiffies - event->created,
- event->func, retired);
- }
- spin_unlock(&group->lock);
- }
- static int events_show(struct seq_file *s, void *unused)
- {
- struct kgsl_device *device = s->private;
- struct kgsl_event_group *group;
- seq_puts(s, "event groups:\n");
- seq_puts(s, "--------------\n");
- read_lock(&device->event_groups_lock);
- list_for_each_entry(group, &device->event_groups, group) {
- events_debugfs_print_group(s, group);
- seq_puts(s, "\n");
- }
- read_unlock(&device->event_groups_lock);
- return 0;
- }
- DEFINE_SHOW_ATTRIBUTE(events);
- void kgsl_device_events_remove(struct kgsl_device *device)
- {
- struct kgsl_event_group *group, *tmp;
- write_lock(&device->event_groups_lock);
- list_for_each_entry_safe(group, tmp, &device->event_groups, group) {
- WARN_ON(!list_empty(&group->events));
- list_del(&group->group);
- }
- write_unlock(&device->event_groups_lock);
- }
- void kgsl_device_events_probe(struct kgsl_device *device)
- {
- INIT_LIST_HEAD(&device->event_groups);
- rwlock_init(&device->event_groups_lock);
- debugfs_create_file("events", 0444, device->d_debugfs, device,
- &events_fops);
- }
- /**
- * kgsl_events_exit() - Destroy the event kmem cache on module exit
- */
- void kgsl_events_exit(void)
- {
- kmem_cache_destroy(events_cache);
- }
- /**
- * kgsl_events_init() - Create the event kmem cache on module start
- */
- void __init kgsl_events_init(void)
- {
- events_cache = KMEM_CACHE(kgsl_event, 0);
- }
|