448 lines
12 KiB
C
448 lines
12 KiB
C
/* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/list.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/debugfs.h>
|
|
#include <kgsl_device.h>
|
|
|
|
#include "kgsl_debugfs.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 struct dentry *events_dentry;
|
|
|
|
static inline void signal_event(struct kgsl_device *device,
|
|
struct kgsl_event *event, int result)
|
|
{
|
|
list_del(&event->node);
|
|
event->result = result;
|
|
trace_printk("ctx:%d, ts:%d, work:0x%p, %pF\n", KGSL_CONTEXT_ID(event->context), event->timestamp, &event->work, __builtin_return_address(0));
|
|
queue_work(device->events_wq, &event->work);
|
|
}
|
|
|
|
/**
|
|
* _kgsl_event_worker() - Work handler for processing GPU event callbacks
|
|
* @work: Pointer to the work_struct for the event
|
|
*
|
|
* Each event callback has its own work struct and is run on a event specific
|
|
* workqeuue. This is the worker that queues up the event callback function.
|
|
*/
|
|
static void _kgsl_event_worker(struct work_struct *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);
|
|
|
|
event->func(event->device, event->group, event->priv, event->result);
|
|
|
|
kgsl_context_put(event->context);
|
|
kmem_cache_free(events_cache, event);
|
|
}
|
|
|
|
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 we aren't racing with the context
|
|
* getting destroyed
|
|
*/
|
|
if (context != NULL && !_kgsl_context_get(context))
|
|
BUG();
|
|
|
|
spin_lock(&group->lock);
|
|
|
|
group->readtimestamp(device, group->priv, KGSL_TIMESTAMP_RETIRED,
|
|
×tamp);
|
|
|
|
/*
|
|
* If no timestamps have been retired since the last time we were here
|
|
* then we can avoid going through this loop
|
|
*/
|
|
if (!flush && timestamp_cmp(timestamp, group->processed) <= 0)
|
|
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);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_process_event_group);
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_flush_event_group);
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_cancel_events_timestamp);
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_cancel_events);
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
spin_unlock(&group->lock);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_cancel_event);
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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;
|
|
trace_printk("ctx:%d, ts:%d, retired:%d, work:0x%p, %pF\n", KGSL_CONTEXT_ID(context), timestamp, retired, &event->work, __builtin_return_address(0));
|
|
queue_work(device->events_wq, &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;
|
|
}
|
|
EXPORT_SYMBOL(kgsl_add_event);
|
|
|
|
static DEFINE_RWLOCK(group_lock);
|
|
static LIST_HEAD(group_list);
|
|
|
|
/**
|
|
* kgsl_process_events() - Work queue for processing new timestamp events
|
|
* @work: Pointer to a work_struct
|
|
*/
|
|
void kgsl_process_events(struct work_struct *work)
|
|
{
|
|
struct kgsl_event_group *group;
|
|
struct kgsl_device *device = container_of(work, struct kgsl_device,
|
|
event_work);
|
|
|
|
trace_printk("work:%p, %pF\n", work, __builtin_return_address(0));
|
|
read_lock(&group_lock);
|
|
list_for_each_entry(group, &group_list, group)
|
|
_process_event_group(device, group, false);
|
|
read_unlock(&group_lock);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_process_events);
|
|
|
|
/**
|
|
* kgsl_del_event_group() - Remove a GPU event group
|
|
* @group: GPU event group to remove
|
|
*/
|
|
void kgsl_del_event_group(struct kgsl_event_group *group)
|
|
{
|
|
/* Make sure that all the events have been deleted from the list */
|
|
BUG_ON(!list_empty(&group->events));
|
|
|
|
write_lock(&group_lock);
|
|
list_del(&group->group);
|
|
write_unlock(&group_lock);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_del_event_group);
|
|
|
|
/**
|
|
* kgsl_add_event_group() - Add a new GPU event group
|
|
* group: Pointer to the new group to add to the list
|
|
* context: Context that owns the group (or NULL for global)
|
|
* name: Name of the group
|
|
* readtimestamp: Function pointer to the readtimestamp function to call when
|
|
* processing events
|
|
* priv: Priv member to pass to the readtimestamp function
|
|
*/
|
|
void kgsl_add_event_group(struct kgsl_event_group *group,
|
|
struct kgsl_context *context, const char *name,
|
|
readtimestamp_func readtimestamp, void *priv)
|
|
{
|
|
BUG_ON(readtimestamp == NULL);
|
|
|
|
spin_lock_init(&group->lock);
|
|
INIT_LIST_HEAD(&group->events);
|
|
|
|
group->context = context;
|
|
group->readtimestamp = readtimestamp;
|
|
group->priv = priv;
|
|
|
|
if (name)
|
|
strlcpy(group->name, name, sizeof(group->name));
|
|
|
|
write_lock(&group_lock);
|
|
list_add_tail(&group->group, &group_list);
|
|
write_unlock(&group_lock);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_add_event_group);
|
|
|
|
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%d:%d age=%lu func=%ps [retired=%d]\n",
|
|
group->context ? group->context->id :
|
|
KGSL_MEMSTORE_GLOBAL,
|
|
event->timestamp, jiffies - event->created,
|
|
event->func, retired);
|
|
}
|
|
spin_unlock(&group->lock);
|
|
}
|
|
|
|
static int events_debugfs_print(struct seq_file *s, void *unused)
|
|
{
|
|
struct kgsl_event_group *group;
|
|
|
|
seq_puts(s, "event groups:\n");
|
|
seq_puts(s, "--------------\n");
|
|
|
|
read_lock(&group_lock);
|
|
list_for_each_entry(group, &group_list, group) {
|
|
events_debugfs_print_group(s, group);
|
|
seq_puts(s, "\n");
|
|
}
|
|
read_unlock(&group_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int events_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, events_debugfs_print, NULL);
|
|
}
|
|
|
|
static const struct file_operations events_fops = {
|
|
.open = events_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
/**
|
|
* kgsl_events_exit() - Destroy the event kmem cache on module exit
|
|
*/
|
|
void kgsl_events_exit(void)
|
|
{
|
|
if (events_cache)
|
|
kmem_cache_destroy(events_cache);
|
|
|
|
debugfs_remove(events_dentry);
|
|
}
|
|
|
|
/**
|
|
* kgsl_events_init() - Create the event kmem cache on module start
|
|
*/
|
|
void __init kgsl_events_init(void)
|
|
{
|
|
struct dentry *debugfs_dir = kgsl_get_debugfs_dir();
|
|
events_cache = KMEM_CACHE(kgsl_event, 0);
|
|
|
|
events_dentry = debugfs_create_file("events", 0444, debugfs_dir, NULL,
|
|
&events_fops);
|
|
|
|
/* Failure to create a debugfs entry is non fatal */
|
|
if (IS_ERR(events_dentry))
|
|
events_dentry = NULL;
|
|
}
|