diff --git a/drivers/gpu/msm/Makefile b/drivers/gpu/msm/Makefile index aacd3552de3..fec53633bbe 100644 --- a/drivers/gpu/msm/Makefile +++ b/drivers/gpu/msm/Makefile @@ -9,7 +9,8 @@ msm_kgsl_core-y = \ kgsl_mmu.o \ kgsl_gpummu.o \ kgsl_iommu.o \ - kgsl_snapshot.o + kgsl_snapshot.o \ + kgsl_events.o msm_kgsl_core-$(CONFIG_DEBUG_FS) += kgsl_debugfs.o msm_kgsl_core-$(CONFIG_MSM_KGSL_CFF_DUMP) += kgsl_cffdump.o diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index 9967934673f..e1f5e317cea 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -52,155 +52,6 @@ MODULE_PARM_DESC(ksgl_mmu_type, static struct ion_client *kgsl_ion_client; -/** - * kgsl_add_event - Add a new timstamp event for the KGSL device - * @device - KGSL device for the new event - * @ts - the timestamp to trigger the event on - * @cb - callback function to call when the timestamp expires - * @priv - private data for the specific event type - * @owner - driver instance that owns this event - * - * @returns - 0 on success or error code on failure - */ - -int kgsl_add_event(struct kgsl_device *device, u32 id, u32 ts, - void (*cb)(struct kgsl_device *, void *, u32, u32), void *priv, - void *owner) -{ - struct kgsl_event *event; - struct list_head *n; - unsigned int cur_ts; - struct kgsl_context *context = NULL; - - if (cb == NULL) - return -EINVAL; - - if (id != KGSL_MEMSTORE_GLOBAL) { - context = idr_find(&device->context_idr, id); - if (context == NULL) - return -EINVAL; - } - cur_ts = kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_RETIRED); - - /* Check to see if the requested timestamp has already fired */ - - if (timestamp_cmp(cur_ts, ts) >= 0) { - cb(device, priv, id, cur_ts); - return 0; - } - - event = kzalloc(sizeof(*event), GFP_KERNEL); - if (event == NULL) - return -ENOMEM; - - event->context = context; - event->timestamp = ts; - event->priv = priv; - event->func = cb; - event->owner = owner; - - /* inc refcount to avoid race conditions in cleanup */ - if (context) - kgsl_context_get(context); - - /* - * Add the event in order to the list. Order is by context id - * first and then by timestamp for that context. - */ - - for (n = device->events.next ; n != &device->events; n = n->next) { - struct kgsl_event *e = - list_entry(n, struct kgsl_event, list); - - if (e->context != context) - continue; - - if (timestamp_cmp(e->timestamp, ts) > 0) { - list_add(&event->list, n->prev); - break; - } - } - - if (n == &device->events) - list_add_tail(&event->list, &device->events); - - queue_work(device->work_queue, &device->ts_expired_ws); - return 0; -} -EXPORT_SYMBOL(kgsl_add_event); - -/** - * kgsl_cancel_events_ctxt - Cancel all events for a context - * @device - KGSL device for the events to cancel - * @ctxt - context whose events we want to cancel - * - */ -static void kgsl_cancel_events_ctxt(struct kgsl_device *device, - struct kgsl_context *context) -{ - struct kgsl_event *event, *event_tmp; - unsigned int id, cur; - - cur = kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_RETIRED); - id = context->id; - - list_for_each_entry_safe(event, event_tmp, &device->events, list) { - if (event->context != context) - continue; - - /* - * "cancel" the events by calling their callback. - * Currently, events are used for lock and memory - * management, so if the process is dying the right - * thing to do is release or free. - */ - if (event->func) - event->func(device, event->priv, id, cur); - - kgsl_context_put(context); - list_del(&event->list); - kfree(event); - } -} - -/** - * kgsl_cancel_events - Cancel all events for a process - * @device - KGSL device for the events to cancel - * @owner - driver instance that owns the events to cancel - * - */ -void kgsl_cancel_events(struct kgsl_device *device, - void *owner) -{ - struct kgsl_event *event, *event_tmp; - unsigned int id, cur; - - list_for_each_entry_safe(event, event_tmp, &device->events, list) { - if (event->owner != owner) - continue; - - cur = kgsl_readtimestamp(device, event->context, - KGSL_TIMESTAMP_RETIRED); - - id = event->context ? event->context->id : KGSL_MEMSTORE_GLOBAL; - /* - * "cancel" the events by calling their callback. - * Currently, events are used for lock and memory - * management, so if the process is dying the right - * thing to do is release or free. - */ - if (event->func) - event->func(device, event->priv, id, cur); - - if (event->context) - kgsl_context_put(event->context); - - list_del(&event->list); - kfree(event); - } -} -EXPORT_SYMBOL(kgsl_cancel_events); - /* kgsl_get_mem_entry - get the mem_entry structure for the specified object * @device - Pointer to the device structure * @ptbase - the pagetable base of the object @@ -379,6 +230,25 @@ kgsl_create_context(struct kgsl_device_private *dev_priv) if (kgsl_sync_timeline_create(context)) { idr_remove(&dev_priv->device->context_idr, id); + goto func_end; + } + + /* Initialize the pending event list */ + INIT_LIST_HEAD(&context->events); + + /* + * Initialize the node that is used to maintain the master list of + * contexts with pending events in the device structure. Normally we + * wouldn't take the time to initalize a node but at event add time we + * call list_empty() on the node as a quick way of determining if the + * context is already in the master list so it needs to always be either + * active or in an unused but initialized state + */ + + INIT_LIST_HEAD(&context->events_list); + +func_end: + if (ret) { kfree(context); return NULL; } @@ -431,55 +301,6 @@ kgsl_context_destroy(struct kref *kref) kfree(context); } -void kgsl_timestamp_expired(struct work_struct *work) -{ - struct kgsl_device *device = container_of(work, struct kgsl_device, - ts_expired_ws); - struct kgsl_event *event, *event_tmp; - uint32_t ts_processed; - unsigned int id; - - mutex_lock(&device->mutex); - - /* Process expired events */ - list_for_each_entry_safe(event, event_tmp, &device->events, list) { - ts_processed = kgsl_readtimestamp(device, event->context, - KGSL_TIMESTAMP_RETIRED); - if (timestamp_cmp(ts_processed, event->timestamp) < 0) - continue; - - id = event->context ? event->context->id : KGSL_MEMSTORE_GLOBAL; - - if (event->func) - event->func(device, event->priv, id, ts_processed); - - if (event->context) - kgsl_context_put(event->context); - - list_del(&event->list); - kfree(event); - } - - /* Send the next pending event for each context to the device */ - if (device->ftbl->next_event) { - unsigned int id = KGSL_MEMSTORE_GLOBAL; - - list_for_each_entry(event, &device->events, list) { - - if (!event->context) - continue; - - if (event->context->id != id) { - device->ftbl->next_event(device, event); - id = event->context->id; - } - } - } - - mutex_unlock(&device->mutex); -} -EXPORT_SYMBOL(kgsl_timestamp_expired); - static void kgsl_check_idle_locked(struct kgsl_device *device) { if (device->pwrctrl.nap_allowed == true && diff --git a/drivers/gpu/msm/kgsl.h b/drivers/gpu/msm/kgsl.h index 57f9bd9a9ad..3935164897f 100644 --- a/drivers/gpu/msm/kgsl.h +++ b/drivers/gpu/msm/kgsl.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2012, The Linux Foundation. All rights reserved. +/* Copyright (c) 2008-2013, 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 @@ -72,6 +72,7 @@ do { _stat += (_size); if (_stat > _max) _max = _stat; } while (0) struct kgsl_device; +struct kgsl_context; struct kgsl_driver { struct cdev cdev; @@ -195,6 +196,9 @@ int kgsl_add_event(struct kgsl_device *device, u32 id, u32 ts, void kgsl_cancel_events(struct kgsl_device *device, void *owner); +void kgsl_cancel_events_ctxt(struct kgsl_device *device, + struct kgsl_context *context); + extern const struct dev_pm_ops kgsl_pm_ops; struct early_suspend; diff --git a/drivers/gpu/msm/kgsl_device.h b/drivers/gpu/msm/kgsl_device.h index 9dbab5b8d5f..0afbd87a6cd 100644 --- a/drivers/gpu/msm/kgsl_device.h +++ b/drivers/gpu/msm/kgsl_device.h @@ -203,6 +203,7 @@ struct kgsl_device { struct pm_qos_request pm_qos_req_dma; struct work_struct ts_expired_ws; struct list_head events; + struct list_head events_pending_list; s64 on_time; /* Postmortem Control switches */ @@ -210,7 +211,8 @@ struct kgsl_device { int pm_ib_enabled; }; -void kgsl_timestamp_expired(struct work_struct *work); +void kgsl_process_events(struct work_struct *work); +void kgsl_check_fences(struct work_struct *work); #define KGSL_DEVICE_COMMON_INIT(_dev) \ .hwaccess_gate = COMPLETION_INITIALIZER((_dev).hwaccess_gate),\ @@ -220,36 +222,42 @@ void kgsl_timestamp_expired(struct work_struct *work); .idle_check_ws = __WORK_INITIALIZER((_dev).idle_check_ws,\ kgsl_idle_check),\ .ts_expired_ws = __WORK_INITIALIZER((_dev).ts_expired_ws,\ - kgsl_timestamp_expired),\ + kgsl_process_events),\ .context_idr = IDR_INIT((_dev).context_idr),\ .events = LIST_HEAD_INIT((_dev).events),\ + .events_pending_list = LIST_HEAD_INIT((_dev).events_pending_list), \ .wait_queue = __WAIT_QUEUE_HEAD_INITIALIZER((_dev).wait_queue),\ .mutex = __MUTEX_INITIALIZER((_dev).mutex),\ .state = KGSL_STATE_INIT,\ .ver_major = DRIVER_VERSION_MAJOR,\ .ver_minor = DRIVER_VERSION_MINOR + +/** + * struct kgsl_context - Master structure for a KGSL context object + * @refcount - kref object for reference counting the context + * @id - integer identifier for the context + * @dev_priv - pointer to the owning device instance + * @devctxt - pointer to the device specific context information + * @reset_status - status indication whether a gpu reset occured and whether + * this context was responsible for causing it + * @wait_on_invalid_ts - flag indicating if this context has tried to wait on a + * bad timestamp + * @timeline - sync timeline used to create fences that can be signaled when a + * sync_pt timestamp expires + * @events - list head of pending events for this context + * @events_list - list node for the list of all contexts that have pending events + */ struct kgsl_context { struct kref refcount; uint32_t id; - - /* Pointer to the owning device instance */ struct kgsl_device_private *dev_priv; - - /* Pointer to the device specific context information */ void *devctxt; - /* - * Status indicating whether a gpu reset occurred and whether this - * context was responsible for causing it - */ unsigned int reset_status; - /* Flag indicating if we tried to wait for bad timestamp for this ctx */ bool wait_on_invalid_ts; - /* - * Timeline used to create fences that can be signaled when a - * sync_pt timestamp expires. - */ struct sync_timeline *timeline; + struct list_head events; + struct list_head events_list; }; struct kgsl_process_private { diff --git a/drivers/gpu/msm/kgsl_events.c b/drivers/gpu/msm/kgsl_events.c new file mode 100644 index 00000000000..20025377ad1 --- /dev/null +++ b/drivers/gpu/msm/kgsl_events.c @@ -0,0 +1,251 @@ +/* Copyright (c) 2011-2013, 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 +#include +#include +#include + +static void _add_event_to_list(struct list_head *head, struct kgsl_event *event) +{ + struct list_head *n; + + for (n = head->next; n != head; n = n->next) { + struct kgsl_event *e = + list_entry(n, struct kgsl_event, list); + + if (timestamp_cmp(e->timestamp, event->timestamp) > 0) { + list_add(&event->list, n->prev); + break; + } + } + + if (n == head) + list_add_tail(&event->list, head); +} + +/** + * kgsl_add_event - Add a new timstamp event for the KGSL device + * @device - KGSL device for the new event + * @id - the context ID that the event should be added to + * @ts - the timestamp to trigger the event on + * @cb - callback function to call when the timestamp expires + * @priv - private data for the specific event type + * @owner - driver instance that owns this event + * + * @returns - 0 on success or error code on failure + */ +int kgsl_add_event(struct kgsl_device *device, u32 id, u32 ts, + void (*cb)(struct kgsl_device *, void *, u32, u32), void *priv, + void *owner) +{ + struct kgsl_event *event; + unsigned int cur_ts; + struct kgsl_context *context = NULL; + + if (cb == NULL) + return -EINVAL; + + if (id != KGSL_MEMSTORE_GLOBAL) { + context = idr_find(&device->context_idr, id); + if (context == NULL) + return -EINVAL; + } + cur_ts = kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_RETIRED); + + /* Check to see if the requested timestamp has already fired */ + + if (timestamp_cmp(cur_ts, ts) >= 0) { + cb(device, priv, id, cur_ts); + return 0; + } + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (event == NULL) + return -ENOMEM; + + event->context = context; + event->timestamp = ts; + event->priv = priv; + event->func = cb; + event->owner = owner; + + /* Add the event to either the owning context or the global list */ + + if (context) { + _add_event_to_list(&context->events, event); + + /* + * Add it to the master list of contexts with pending events if + * it isn't already there + */ + + if (list_empty(&context->events_list)) + list_add_tail(&context->events_list, + &device->events_pending_list); + + } else + _add_event_to_list(&device->events, event); + + queue_work(device->work_queue, &device->ts_expired_ws); + return 0; +} +EXPORT_SYMBOL(kgsl_add_event); + +/** + * kgsl_cancel_events_ctxt - Cancel all events for a context + * @device - KGSL device for the events to cancel + * @context - context whose events we want to cancel + * + */ +void kgsl_cancel_events_ctxt(struct kgsl_device *device, + struct kgsl_context *context) +{ + struct kgsl_event *event, *event_tmp; + unsigned int id, cur; + + cur = kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_RETIRED); + id = context->id; + + list_for_each_entry_safe(event, event_tmp, &context->events, list) { + /* + * "cancel" the events by calling their callback. + * Currently, events are used for lock and memory + * management, so if the process is dying the right + * thing to do is release or free. + */ + if (event->func) + event->func(device, event->priv, id, cur); + + list_del(&event->list); + kfree(event); + } + + /* Remove ourselves from the master pending list */ + list_del_init(&context->events_list); +} + +/** + * kgsl_cancel_events - Cancel all generic events for a process + * @device - KGSL device for the events to cancel + * @owner - driver instance that owns the events to cancel + * + */ +void kgsl_cancel_events(struct kgsl_device *device, + void *owner) +{ + struct kgsl_event *event, *event_tmp; + unsigned int cur; + + cur = kgsl_readtimestamp(device, NULL, KGSL_TIMESTAMP_RETIRED); + + list_for_each_entry_safe(event, event_tmp, &device->events, list) { + if (event->owner != owner) + continue; + + /* + * "cancel" the events by calling their callback. + * Currently, events are used for lock and memory + * management, so if the process is dying the right + * thing to do is release or free. + */ + if (event->func) + event->func(device, event->priv, KGSL_MEMSTORE_GLOBAL, + cur); + + list_del(&event->list); + kfree(event); + } +} +EXPORT_SYMBOL(kgsl_cancel_events); + +static void _process_event_list(struct kgsl_device *device, + struct list_head *head, unsigned int timestamp) +{ + struct kgsl_event *event, *tmp; + unsigned int id; + + list_for_each_entry_safe(event, tmp, head, list) { + if (timestamp_cmp(timestamp, event->timestamp) < 0) + break; + + id = event->context ? event->context->id : KGSL_MEMSTORE_GLOBAL; + + if (event->func) + event->func(device, event->priv, id, timestamp); + + list_del(&event->list); + kfree(event); + } +} + +static inline void _mark_next_event(struct kgsl_device *device, + struct list_head *head) +{ + struct kgsl_event *event; + + if (!list_empty(head)) { + event = list_first_entry(head, struct kgsl_event, list); + device->ftbl->next_event(device, event); + } +} + +static int kgsl_process_context_events(struct kgsl_device *device, + struct kgsl_context *context) +{ + unsigned int timestamp = kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_RETIRED); + + _process_event_list(device, &context->events, timestamp); + + /* Mark the next pending event on the list to fire an interrupt */ + _mark_next_event(device, &context->events); + + /* + * Return 0 if the list is empty so the calling function can remove the + * context from the pending list + */ + + return list_empty(&context->events) ? 0 : 1; +} + +void kgsl_process_events(struct work_struct *work) +{ + struct kgsl_device *device = container_of(work, struct kgsl_device, + ts_expired_ws); + struct kgsl_context *context, *tmp; + uint32_t timestamp; + + mutex_lock(&device->mutex); + + /* Process expired global events */ + timestamp = kgsl_readtimestamp(device, NULL, KGSL_TIMESTAMP_RETIRED); + _process_event_list(device, &device->events, timestamp); + _mark_next_event(device, &device->events); + + /* Now process all of the pending contexts */ + list_for_each_entry_safe(context, tmp, &device->events_pending_list, + events_list) { + + /* + * If kgsl_timestamp_expired_context returns 0 then it no longer + * has any pending events and can be removed from the list + */ + + if (kgsl_process_context_events(device, context) == 0) + list_del_init(&context->events_list); + } + + mutex_unlock(&device->mutex); +} +EXPORT_SYMBOL(kgsl_process_events);