summaryrefslogtreecommitdiff
path: root/drivers/staging/psb/psb_scene.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/psb/psb_scene.c')
-rw-r--r--drivers/staging/psb/psb_scene.c523
1 files changed, 523 insertions, 0 deletions
diff --git a/drivers/staging/psb/psb_scene.c b/drivers/staging/psb/psb_scene.c
new file mode 100644
index 000000000000..88c151731364
--- /dev/null
+++ b/drivers/staging/psb/psb_scene.c
@@ -0,0 +1,523 @@
+/**************************************************************************
+ * Copyright (c) 2007, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to
+ * develop this driver.
+ *
+ **************************************************************************/
+/*
+ * Authors: Thomas Hellstrom <thomas-at-tungstengraphics.com>
+ */
+
+#include <drm/drmP.h>
+#include "psb_drv.h"
+#include "psb_scene.h"
+
+void psb_clear_scene_atomic(struct psb_scene *scene)
+{
+ int i;
+ struct page *page;
+ void *v;
+
+ for (i = 0; i < scene->clear_num_pages; ++i) {
+ page = ttm_tt_get_page(scene->hw_data->ttm,
+ scene->clear_p_start + i);
+ if (in_irq())
+ v = kmap_atomic(page, KM_IRQ0);
+ else
+ v = kmap_atomic(page, KM_USER0);
+
+ memset(v, 0, PAGE_SIZE);
+
+ if (in_irq())
+ kunmap_atomic(v, KM_IRQ0);
+ else
+ kunmap_atomic(v, KM_USER0);
+ }
+}
+
+int psb_clear_scene(struct psb_scene *scene)
+{
+ struct ttm_bo_kmap_obj bmo;
+ bool is_iomem;
+ void *addr;
+
+ int ret = ttm_bo_kmap(scene->hw_data, scene->clear_p_start,
+ scene->clear_num_pages, &bmo);
+
+ PSB_DEBUG_RENDER("Scene clear.\n");
+ if (ret)
+ return ret;
+
+ addr = ttm_kmap_obj_virtual(&bmo, &is_iomem);
+ BUG_ON(is_iomem);
+ memset(addr, 0, scene->clear_num_pages << PAGE_SHIFT);
+ ttm_bo_kunmap(&bmo);
+
+ return 0;
+}
+
+static void psb_destroy_scene(struct kref *kref)
+{
+ struct psb_scene *scene =
+ container_of(kref, struct psb_scene, kref);
+
+ PSB_DEBUG_RENDER("Scene destroy.\n");
+ psb_scheduler_remove_scene_refs(scene);
+ ttm_bo_unref(&scene->hw_data);
+ drm_free(scene, sizeof(*scene), DRM_MEM_DRIVER);
+}
+
+void psb_scene_unref(struct psb_scene **p_scene)
+{
+ struct psb_scene *scene = *p_scene;
+
+ PSB_DEBUG_RENDER("Scene unref.\n");
+ *p_scene = NULL;
+ kref_put(&scene->kref, &psb_destroy_scene);
+}
+
+struct psb_scene *psb_scene_ref(struct psb_scene *src)
+{
+ PSB_DEBUG_RENDER("Scene ref.\n");
+ kref_get(&src->kref);
+ return src;
+}
+
+static struct psb_scene *psb_alloc_scene(struct drm_device *dev,
+ uint32_t w, uint32_t h)
+{
+ struct drm_psb_private *dev_priv =
+ (struct drm_psb_private *) dev->dev_private;
+ struct ttm_bo_device *bdev = &dev_priv->bdev;
+ int ret = -EINVAL;
+ struct psb_scene *scene;
+ uint32_t bo_size;
+ struct psb_xhw_buf buf;
+
+ PSB_DEBUG_RENDER("Alloc scene w %u h %u msaa %u\n", w & 0xffff, h,
+ w >> 16);
+
+ scene = drm_calloc(1, sizeof(*scene), DRM_MEM_DRIVER);
+
+ if (!scene) {
+ DRM_ERROR("Out of memory allocating scene object.\n");
+ return NULL;
+ }
+
+ scene->dev = dev;
+ scene->w = w;
+ scene->h = h;
+ scene->hw_scene = NULL;
+ kref_init(&scene->kref);
+
+ INIT_LIST_HEAD(&buf.head);
+ ret = psb_xhw_scene_info(dev_priv, &buf, scene->w, scene->h,
+ scene->hw_cookie, &bo_size,
+ &scene->clear_p_start,
+ &scene->clear_num_pages);
+ if (ret)
+ goto out_err;
+
+ ret = ttm_buffer_object_create(bdev, bo_size, ttm_bo_type_kernel,
+ DRM_PSB_FLAG_MEM_MMU |
+ TTM_PL_FLAG_CACHED,
+ 0, 0, 1, NULL, &scene->hw_data);
+ if (ret)
+ goto out_err;
+
+ return scene;
+out_err:
+ drm_free(scene, sizeof(*scene), DRM_MEM_DRIVER);
+ return NULL;
+}
+
+int psb_validate_scene_pool(struct psb_context *context,
+ struct psb_scene_pool *pool,
+ uint32_t w,
+ uint32_t h,
+ int final_pass, struct psb_scene **scene_p)
+{
+ struct drm_device *dev = pool->dev;
+ struct drm_psb_private *dev_priv =
+ (struct drm_psb_private *) dev->dev_private;
+ struct psb_scene *scene = pool->scenes[pool->cur_scene];
+ int ret;
+ unsigned long irq_flags;
+ struct psb_scheduler *scheduler = &dev_priv->scheduler;
+ uint32_t bin_pt_offset;
+ uint32_t bin_param_offset;
+
+ PSB_DEBUG_RENDER("Validate scene pool. Scene %u\n",
+ pool->cur_scene);
+
+ if (unlikely(!dev_priv->ta_mem)) {
+ dev_priv->ta_mem =
+ psb_alloc_ta_mem(dev, dev_priv->ta_mem_pages);
+ if (!dev_priv->ta_mem)
+ return -ENOMEM;
+
+ bin_pt_offset = ~0;
+ bin_param_offset = ~0;
+ } else {
+ bin_pt_offset = dev_priv->ta_mem->hw_data->offset;
+ bin_param_offset = dev_priv->ta_mem->ta_memory->offset;
+ }
+
+ pool->w = w;
+ pool->h = h;
+ if (scene && (scene->w != pool->w || scene->h != pool->h)) {
+ spin_lock_irqsave(&scheduler->lock, irq_flags);
+ if (scene->flags & PSB_SCENE_FLAG_DIRTY) {
+ spin_unlock_irqrestore(&scheduler->lock,
+ irq_flags);
+ DRM_ERROR("Trying to resize a dirty scene.\n");
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&scheduler->lock, irq_flags);
+ psb_scene_unref(&pool->scenes[pool->cur_scene]);
+ scene = NULL;
+ }
+
+ if (!scene) {
+ pool->scenes[pool->cur_scene] = scene =
+ psb_alloc_scene(pool->dev, pool->w, pool->h);
+
+ if (!scene)
+ return -ENOMEM;
+
+ scene->flags = PSB_SCENE_FLAG_CLEARED;
+ }
+
+ ret = psb_validate_kernel_buffer(context, scene->hw_data,
+ PSB_ENGINE_TA,
+ PSB_BO_FLAG_SCENE |
+ PSB_GPU_ACCESS_READ |
+ PSB_GPU_ACCESS_WRITE, 0);
+ if (unlikely(ret != 0))
+ return ret;
+
+ /*
+ * FIXME: We need atomic bit manipulation here for the
+ * scheduler. For now use the spinlock.
+ */
+
+ spin_lock_irqsave(&scheduler->lock, irq_flags);
+ if (!(scene->flags & PSB_SCENE_FLAG_CLEARED)) {
+ spin_unlock_irqrestore(&scheduler->lock, irq_flags);
+ PSB_DEBUG_RENDER("Waiting to clear scene memory.\n");
+ mutex_lock(&scene->hw_data->mutex);
+
+ ret = ttm_bo_wait(scene->hw_data, 0, 1, 0);
+ mutex_unlock(&scene->hw_data->mutex);
+ if (ret)
+ return ret;
+
+ ret = psb_clear_scene(scene);
+
+ if (ret)
+ return ret;
+ spin_lock_irqsave(&scheduler->lock, irq_flags);
+ scene->flags |= PSB_SCENE_FLAG_CLEARED;
+ }
+ spin_unlock_irqrestore(&scheduler->lock, irq_flags);
+
+ ret = psb_validate_kernel_buffer(context, dev_priv->ta_mem->hw_data,
+ PSB_ENGINE_TA,
+ PSB_BO_FLAG_SCENE |
+ PSB_GPU_ACCESS_READ |
+ PSB_GPU_ACCESS_WRITE, 0);
+ if (unlikely(ret != 0))
+ return ret;
+
+ ret =
+ psb_validate_kernel_buffer(context,
+ dev_priv->ta_mem->ta_memory,
+ PSB_ENGINE_TA,
+ PSB_BO_FLAG_SCENE |
+ PSB_GPU_ACCESS_READ |
+ PSB_GPU_ACCESS_WRITE, 0);
+
+ if (unlikely(ret != 0))
+ return ret;
+
+ if (unlikely(bin_param_offset !=
+ dev_priv->ta_mem->ta_memory->offset ||
+ bin_pt_offset !=
+ dev_priv->ta_mem->hw_data->offset ||
+ dev_priv->force_ta_mem_load)) {
+
+ struct psb_xhw_buf buf;
+
+ INIT_LIST_HEAD(&buf.head);
+ ret = psb_xhw_ta_mem_load(dev_priv, &buf,
+ PSB_TA_MEM_FLAG_TA |
+ PSB_TA_MEM_FLAG_RASTER |
+ PSB_TA_MEM_FLAG_HOSTA |
+ PSB_TA_MEM_FLAG_HOSTD |
+ PSB_TA_MEM_FLAG_INIT,
+ dev_priv->ta_mem->ta_memory->
+ offset,
+ dev_priv->ta_mem->hw_data->
+ offset,
+ dev_priv->ta_mem->hw_cookie);
+ if (ret)
+ return ret;
+
+ dev_priv->force_ta_mem_load = 0;
+ }
+
+ if (final_pass) {
+
+ /*
+ * Clear the scene on next use. Advance the scene counter.
+ */
+
+ spin_lock_irqsave(&scheduler->lock, irq_flags);
+ scene->flags &= ~PSB_SCENE_FLAG_CLEARED;
+ spin_unlock_irqrestore(&scheduler->lock, irq_flags);
+ pool->cur_scene = (pool->cur_scene + 1) % pool->num_scenes;
+ }
+
+ *scene_p = psb_scene_ref(scene);
+ return 0;
+}
+
+static void psb_scene_pool_destroy(struct kref *kref)
+{
+ struct psb_scene_pool *pool =
+ container_of(kref, struct psb_scene_pool, kref);
+ int i;
+ PSB_DEBUG_RENDER("Scene pool destroy.\n");
+
+ for (i = 0; i < pool->num_scenes; ++i) {
+ PSB_DEBUG_RENDER("scenes %d is 0x%08lx\n", i,
+ (unsigned long) pool->scenes[i]);
+ if (pool->scenes[i])
+ psb_scene_unref(&pool->scenes[i]);
+ }
+
+ drm_free(pool, sizeof(*pool), DRM_MEM_DRIVER);
+}
+
+void psb_scene_pool_unref(struct psb_scene_pool **p_pool)
+{
+ struct psb_scene_pool *pool = *p_pool;
+
+ PSB_DEBUG_RENDER("Scene pool unref\n");
+ *p_pool = NULL;
+ kref_put(&pool->kref, &psb_scene_pool_destroy);
+}
+
+struct psb_scene_pool *psb_scene_pool_ref(struct psb_scene_pool *src)
+{
+ kref_get(&src->kref);
+ return src;
+}
+
+/*
+ * Callback for base object manager.
+ */
+
+static void psb_scene_pool_release(struct ttm_base_object **p_base)
+{
+ struct ttm_base_object *base = *p_base;
+ struct psb_scene_pool *pool =
+ container_of(base, struct psb_scene_pool, base);
+ *p_base = NULL;
+
+ psb_scene_pool_unref(&pool);
+}
+
+struct psb_scene_pool *psb_scene_pool_lookup(struct drm_file *file_priv,
+ uint32_t handle,
+ int check_owner)
+{
+ struct ttm_object_file *tfile = psb_fpriv(file_priv)->tfile;
+ struct ttm_base_object *base;
+ struct psb_scene_pool *pool;
+
+
+ base = ttm_base_object_lookup(tfile, handle);
+ if (!base || (base->object_type != PSB_USER_OBJECT_SCENE_POOL)) {
+ DRM_ERROR("Could not find scene pool object 0x%08x\n",
+ handle);
+ return NULL;
+ }
+
+ if (check_owner && tfile != base->tfile && !base->shareable) {
+ ttm_base_object_unref(&base);
+ return NULL;
+ }
+
+ pool = container_of(base, struct psb_scene_pool, base);
+ kref_get(&pool->kref);
+ ttm_base_object_unref(&base);
+ return pool;
+}
+
+struct psb_scene_pool *psb_scene_pool_alloc(struct drm_file *file_priv,
+ int shareable,
+ uint32_t num_scenes,
+ uint32_t w, uint32_t h)
+{
+ struct ttm_object_file *tfile = psb_fpriv(file_priv)->tfile;
+ struct drm_device *dev = file_priv->minor->dev;
+ struct psb_scene_pool *pool;
+ int ret;
+
+ PSB_DEBUG_RENDER("Scene pool alloc\n");
+ pool = drm_calloc(1, sizeof(*pool), DRM_MEM_DRIVER);
+ if (!pool) {
+ DRM_ERROR("Out of memory allocating scene pool object.\n");
+ return NULL;
+ }
+ pool->w = w;
+ pool->h = h;
+ pool->dev = dev;
+ pool->num_scenes = num_scenes;
+ kref_init(&pool->kref);
+
+ /*
+ * The base object holds a reference.
+ */
+
+ kref_get(&pool->kref);
+ ret = ttm_base_object_init(tfile, &pool->base, shareable,
+ PSB_USER_OBJECT_SCENE_POOL,
+ &psb_scene_pool_release, NULL);
+ if (unlikely(ret != 0))
+ goto out_err;
+
+ return pool;
+out_err:
+ drm_free(pool, sizeof(*pool), DRM_MEM_DRIVER);
+ return NULL;
+}
+
+/*
+ * Code to support multiple ta memory buffers.
+ */
+
+static void psb_ta_mem_destroy(struct kref *kref)
+{
+ struct psb_ta_mem *ta_mem =
+ container_of(kref, struct psb_ta_mem, kref);
+
+ ttm_bo_unref(&ta_mem->hw_data);
+ ttm_bo_unref(&ta_mem->ta_memory);
+ drm_free(ta_mem, sizeof(*ta_mem), DRM_MEM_DRIVER);
+}
+
+void psb_ta_mem_unref(struct psb_ta_mem **p_ta_mem)
+{
+ struct psb_ta_mem *ta_mem = *p_ta_mem;
+ *p_ta_mem = NULL;
+ kref_put(&ta_mem->kref, psb_ta_mem_destroy);
+}
+
+struct psb_ta_mem *psb_ta_mem_ref(struct psb_ta_mem *src)
+{
+ kref_get(&src->kref);
+ return src;
+}
+
+struct psb_ta_mem *psb_alloc_ta_mem(struct drm_device *dev, uint32_t pages)
+{
+ struct drm_psb_private *dev_priv =
+ (struct drm_psb_private *) dev->dev_private;
+ struct ttm_bo_device *bdev = &dev_priv->bdev;
+ int ret = -EINVAL;
+ struct psb_ta_mem *ta_mem;
+ uint32_t bo_size;
+ uint32_t ta_min_size;
+ struct psb_xhw_buf buf;
+
+ INIT_LIST_HEAD(&buf.head);
+
+ ta_mem = drm_calloc(1, sizeof(*ta_mem), DRM_MEM_DRIVER);
+
+ if (!ta_mem) {
+ DRM_ERROR("Out of memory allocating parameter memory.\n");
+ return NULL;
+ }
+
+ kref_init(&ta_mem->kref);
+ ret = psb_xhw_ta_mem_info(dev_priv, &buf, pages,
+ ta_mem->hw_cookie,
+ &bo_size,
+ &ta_min_size);
+ if (ret == -ENOMEM) {
+ DRM_ERROR("Parameter memory size is too small.\n");
+ DRM_INFO("Attempted to use %u kiB of parameter memory.\n",
+ (unsigned int) (pages * (PAGE_SIZE / 1024)));
+ DRM_INFO("The Xpsb driver thinks this is too small and\n");
+ DRM_INFO("suggests %u kiB. Check the psb DRM\n",
+ (unsigned int)(ta_min_size / 1024));
+ DRM_INFO("\"ta_mem_size\" parameter!\n");
+ }
+ if (ret)
+ goto out_err0;
+
+ ret = ttm_buffer_object_create(bdev, bo_size, ttm_bo_type_kernel,
+ DRM_PSB_FLAG_MEM_MMU,
+ 0, 0, 0, NULL,
+ &ta_mem->hw_data);
+ if (ret)
+ goto out_err0;
+
+ bo_size = pages * PAGE_SIZE;
+ ret =
+ ttm_buffer_object_create(bdev, bo_size,
+ ttm_bo_type_kernel,
+ DRM_PSB_FLAG_MEM_RASTGEOM,
+ 0,
+ 1024 * 1024 >> PAGE_SHIFT, 0,
+ NULL,
+ &ta_mem->ta_memory);
+ if (ret)
+ goto out_err1;
+
+ return ta_mem;
+out_err1:
+ ttm_bo_unref(&ta_mem->hw_data);
+out_err0:
+ drm_free(ta_mem, sizeof(*ta_mem), DRM_MEM_DRIVER);
+ return NULL;
+}
+
+int drm_psb_scene_unref_ioctl(struct drm_device *dev,
+ void *data, struct drm_file *file_priv)
+{
+ struct ttm_object_file *tfile = psb_fpriv(file_priv)->tfile;
+ struct drm_psb_scene *scene = (struct drm_psb_scene *) data;
+ int ret = 0;
+ struct drm_psb_private *dev_priv = psb_priv(dev);
+ if (!scene->handle_valid)
+ return 0;
+ down_read(&dev_priv->sgx_sem);
+ psb_check_power_state(dev, PSB_DEVICE_SGX);
+
+ ret =
+ ttm_ref_object_base_unref(tfile, scene->handle, TTM_REF_USAGE);
+ if (unlikely(ret != 0))
+ DRM_ERROR("Could not unreference a scene object.\n");
+ up_read(&dev_priv->sgx_sem);
+ if (drm_psb_ospm && IS_MRST(dev))
+ schedule_delayed_work(&dev_priv->scheduler.wq, 1);
+ return ret;
+}