linux-surface/drivers/gpu/drm/i915/intel_ipts.c

622 lines
15 KiB
C

/*
* Copyright 2016 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/intel_ipts_if.h>
#include <drm/drmP.h>
#include "i915_drv.h"
#define SUPPORTED_IPTS_INTERFACE_VERSION 1
#define REACQUIRE_DB_THRESHOLD 8
#define DB_LOST_CHECK_STEP1_INTERVAL 2000 /* ms */
#define DB_LOST_CHECK_STEP2_INTERVAL 500 /* ms */
/* intel IPTS ctx for ipts support */
typedef struct intel_ipts {
struct drm_device *dev;
struct i915_gem_context *ipts_context;
intel_ipts_callback_t ipts_clbks;
/* buffers' list */
struct {
spinlock_t lock;
struct list_head list;
} buffers;
void *data;
struct delayed_work reacquire_db_work;
intel_ipts_wq_info_t wq_info;
u32 old_tail;
u32 old_head;
bool need_reacquire_db;
bool connected;
bool initialized;
} intel_ipts_t;
intel_ipts_t intel_ipts;
typedef struct intel_ipts_object {
struct list_head list;
struct drm_i915_gem_object *gem_obj;
void *cpu_addr;
} intel_ipts_object_t;
static intel_ipts_object_t *ipts_object_create(size_t size, u32 flags)
{
intel_ipts_object_t *obj = NULL;
struct drm_i915_gem_object *gem_obj = NULL;
int ret = 0;
obj = kzalloc(sizeof(*obj), GFP_KERNEL);
if (!obj)
return NULL;
size = roundup(size, PAGE_SIZE);
if (size == 0) {
ret = -EINVAL;
goto err_out;
}
/* Allocate the new object */
gem_obj = i915_gem_object_create(intel_ipts.dev, size);
if (gem_obj == NULL) {
ret = -ENOMEM;
goto err_out;
}
if (flags & IPTS_BUF_FLAG_CONTIGUOUS) {
ret = i915_gem_object_attach_phys(gem_obj, PAGE_SIZE);
if (ret) {
pr_info(">> ipts no contiguous : %d\n", ret);
goto err_out;
}
}
obj->gem_obj = gem_obj;
spin_lock(&intel_ipts.buffers.lock);
list_add_tail(&obj->list, &intel_ipts.buffers.list);
spin_unlock(&intel_ipts.buffers.lock);
return obj;
err_out:
if (gem_obj)
i915_gem_free_object(&gem_obj->base);
if (obj)
kfree(obj);
return NULL;
}
static void ipts_object_free(intel_ipts_object_t* obj)
{
spin_lock(&intel_ipts.buffers.lock);
list_del(&obj->list);
spin_unlock(&intel_ipts.buffers.lock);
i915_gem_free_object(&obj->gem_obj->base);
kfree(obj);
}
static int ipts_object_pin(intel_ipts_object_t* obj,
struct i915_gem_context *ipts_ctx)
{
struct i915_address_space *vm = NULL;
struct i915_vma *vma = NULL;
struct drm_i915_private *dev_priv = intel_ipts.dev->dev_private;
int ret = 0;
if (ipts_ctx->ppgtt) {
vm = &ipts_ctx->ppgtt->base;
} else {
vm = &dev_priv->ggtt.base;
}
vma = i915_gem_obj_lookup_or_create_vma(obj->gem_obj, vm, NULL);
if (IS_ERR(vma)) {
DRM_ERROR("cannot find or create vma\n");
return -1;
}
ret = i915_vma_pin(vma, 0, PAGE_SIZE, PIN_USER);
return ret;
}
static void ipts_object_unpin(intel_ipts_object_t *obj)
{
/* TBD: Add support */
}
static void* ipts_object_map(intel_ipts_object_t *obj)
{
return i915_gem_object_pin_map(obj->gem_obj, I915_MAP_WB);
}
static void ipts_object_unmap(intel_ipts_object_t* obj)
{
i915_gem_object_unpin_map(obj->gem_obj);
obj->cpu_addr = NULL;
}
static int create_ipts_context(void)
{
struct i915_gem_context *ipts_ctx = NULL;
struct drm_i915_private *dev_priv = intel_ipts.dev->dev_private;
int ret = 0;
/* Initialize the context right away.*/
ret = i915_mutex_lock_interruptible(intel_ipts.dev);
if (ret) {
DRM_ERROR("i915_mutex_lock_interruptible failed \n");
return ret;
}
ipts_ctx = i915_gem_context_create_ipts(dev_priv);
if (IS_ERR(ipts_ctx)) {
DRM_ERROR("Failed to create IPTS context (error %ld)\n",
PTR_ERR(ipts_ctx));
ret = PTR_ERR(ipts_ctx);
goto err_unlock;
}
ret = execlists_context_deferred_alloc(ipts_ctx, dev_priv->engine[RCS]);
if (ret) {
DRM_DEBUG("lr context allocation failed : %d\n", ret);
goto err_ctx;
}
ret = execlists_context_pin(dev_priv->engine[RCS], ipts_ctx);
if (ret) {
DRM_DEBUG("lr context pinning failed : %d\n", ret);
goto err_ctx;
}
/* Release the mutex */
mutex_unlock(&intel_ipts.dev->struct_mutex);
spin_lock_init(&intel_ipts.buffers.lock);
INIT_LIST_HEAD(&intel_ipts.buffers.list);
intel_ipts.ipts_context = ipts_ctx;
return 0;
err_ctx:
if (ipts_ctx)
i915_gem_context_put(ipts_ctx);
err_unlock:
mutex_unlock(&intel_ipts.dev->struct_mutex);
return ret;
}
static void destroy_ipts_context(void)
{
struct i915_gem_context *ipts_ctx = NULL;
struct drm_i915_private *dev_priv = intel_ipts.dev->dev_private;
int ret = 0;
ipts_ctx = intel_ipts.ipts_context;
/* Initialize the context right away.*/
ret = i915_mutex_lock_interruptible(intel_ipts.dev);
if (ret) {
DRM_ERROR("i915_mutex_lock_interruptible failed \n");
return;
}
execlists_context_unpin(dev_priv->engine[RCS], ipts_ctx);
i915_gem_context_put(ipts_ctx);
mutex_unlock(&intel_ipts.dev->struct_mutex);
}
int intel_ipts_notify_complete(void)
{
if (intel_ipts.ipts_clbks.workload_complete)
intel_ipts.ipts_clbks.workload_complete(intel_ipts.data);
return 0;
}
int intel_ipts_notify_backlight_status(bool backlight_on)
{
if (intel_ipts.ipts_clbks.notify_gfx_status) {
if (backlight_on) {
intel_ipts.ipts_clbks.notify_gfx_status(
IPTS_NOTIFY_STA_BACKLIGHT_ON,
intel_ipts.data);
schedule_delayed_work(&intel_ipts.reacquire_db_work,
msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL));
} else {
intel_ipts.ipts_clbks.notify_gfx_status(
IPTS_NOTIFY_STA_BACKLIGHT_OFF,
intel_ipts.data);
cancel_delayed_work(&intel_ipts.reacquire_db_work);
}
}
return 0;
}
static void intel_ipts_reacquire_db(intel_ipts_t *intel_ipts_p)
{
int ret = 0;
ret = i915_mutex_lock_interruptible(intel_ipts_p->dev);
if (ret) {
DRM_ERROR("i915_mutex_lock_interruptible failed \n");
return;
}
/* Reacquire the doorbell */
i915_guc_ipts_reacquire_doorbell(intel_ipts_p->dev->dev_private);
mutex_unlock(&intel_ipts_p->dev->struct_mutex);
return;
}
static int intel_ipts_get_wq_info(uint64_t gfx_handle,
intel_ipts_wq_info_t *wq_info)
{
if (gfx_handle != (uint64_t)&intel_ipts) {
DRM_ERROR("invalid gfx handle\n");
return -EINVAL;
}
*wq_info = intel_ipts.wq_info;
intel_ipts_reacquire_db(&intel_ipts);
schedule_delayed_work(&intel_ipts.reacquire_db_work,
msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL));
return 0;
}
static int set_wq_info(void)
{
struct drm_i915_private *dev_priv = intel_ipts.dev->dev_private;
struct intel_guc *guc = &dev_priv->guc;
struct i915_guc_client *client;
struct guc_process_desc *desc;
void *base = NULL;
intel_ipts_wq_info_t *wq_info;
u64 phy_base = 0;
wq_info = &intel_ipts.wq_info;
client = guc->ipts_client;
if (!client) {
DRM_ERROR("IPTS GuC client is NOT available\n");
return -EINVAL;
}
base = client->client_base;
desc = (struct guc_process_desc *)((u64)base + client->proc_desc_offset);
desc->wq_base_addr = (u64)base + client->wq_offset;
desc->db_base_addr = (u64)base + client->doorbell_offset;
/* IPTS expects physical addresses to pass it to ME */
phy_base = sg_dma_address(client->vma->pages->sgl);
wq_info->db_addr = desc->db_base_addr;
wq_info->db_phy_addr = phy_base + client->doorbell_offset;
wq_info->db_cookie_offset = offsetof(struct guc_doorbell_info, cookie);
wq_info->wq_addr = desc->wq_base_addr;
wq_info->wq_phy_addr = phy_base + client->wq_offset;
wq_info->wq_head_addr = (u64)&desc->head;
wq_info->wq_head_phy_addr = phy_base + client->proc_desc_offset +
offsetof(struct guc_process_desc, head);
wq_info->wq_tail_addr = (u64)&desc->tail;
wq_info->wq_tail_phy_addr = phy_base + client->proc_desc_offset +
offsetof(struct guc_process_desc, tail);
wq_info->wq_size = desc->wq_size_bytes;
return 0;
}
static int intel_ipts_init_wq(void)
{
int ret = 0;
ret = i915_mutex_lock_interruptible(intel_ipts.dev);
if (ret) {
DRM_ERROR("i915_mutex_lock_interruptible failed\n");
return ret;
}
/* disable IPTS submission */
i915_guc_ipts_submission_disable(intel_ipts.dev->dev_private);
/* enable IPTS submission */
ret = i915_guc_ipts_submission_enable(intel_ipts.dev->dev_private,
intel_ipts.ipts_context);
if (ret) {
DRM_ERROR("i915_guc_ipts_submission_enable failed : %d\n", ret);
goto out;
}
ret = set_wq_info();
if (ret) {
DRM_ERROR("set_wq_info failed\n");
goto out;
}
out:
mutex_unlock(&intel_ipts.dev->struct_mutex);
return ret;
}
static void intel_ipts_release_wq(void)
{
int ret = 0;
ret = i915_mutex_lock_interruptible(intel_ipts.dev);
if (ret) {
DRM_ERROR("i915_mutex_lock_interruptible failed\n");
return;
}
/* disable IPTS submission */
i915_guc_ipts_submission_disable(intel_ipts.dev->dev_private);
mutex_unlock(&intel_ipts.dev->struct_mutex);
}
static int intel_ipts_map_buffer(u64 gfx_handle, intel_ipts_mapbuffer_t *mapbuf)
{
intel_ipts_object_t* obj;
struct i915_gem_context *ipts_ctx = NULL;
struct drm_i915_private *dev_priv = intel_ipts.dev->dev_private;
struct i915_address_space *vm = NULL;
struct i915_vma *vma = NULL;
int ret = 0;
if (gfx_handle != (uint64_t)&intel_ipts) {
DRM_ERROR("invalid gfx handle\n");
return -EINVAL;
}
/* Acquire mutex first */
ret = i915_mutex_lock_interruptible(intel_ipts.dev);
if (ret) {
DRM_ERROR("i915_mutex_lock_interruptible failed \n");
return -EINVAL;
}
obj = ipts_object_create(mapbuf->size, mapbuf->flags);
if (!obj)
return -ENOMEM;
ipts_ctx = intel_ipts.ipts_context;
ret = ipts_object_pin(obj, ipts_ctx);
if (ret) {
DRM_ERROR("Not able to pin iTouch obj\n");
ipts_object_free(obj);
mutex_unlock(&intel_ipts.dev->struct_mutex);
return -ENOMEM;
}
if (mapbuf->flags & IPTS_BUF_FLAG_CONTIGUOUS) {
obj->cpu_addr = obj->gem_obj->phys_handle->vaddr;
} else {
obj->cpu_addr = ipts_object_map(obj);
}
if (ipts_ctx->ppgtt) {
vm = &ipts_ctx->ppgtt->base;
} else {
vm = &dev_priv->ggtt.base;
}
vma = i915_gem_obj_lookup_or_create_vma(obj->gem_obj, vm, NULL);
if (IS_ERR(vma)) {
DRM_ERROR("cannot find or create vma\n");
return -EINVAL;
}
mapbuf->gfx_addr = (void*)vma->node.start;
mapbuf->cpu_addr = (void*)obj->cpu_addr;
mapbuf->buf_handle = (u64)obj;
if (mapbuf->flags & IPTS_BUF_FLAG_CONTIGUOUS) {
mapbuf->phy_addr = (u64)obj->gem_obj->phys_handle->busaddr;
}
/* Release the mutex */
mutex_unlock(&intel_ipts.dev->struct_mutex);
return 0;
}
static int intel_ipts_unmap_buffer(uint64_t gfx_handle, uint64_t buf_handle)
{
intel_ipts_object_t* obj = (intel_ipts_object_t*)buf_handle;
if (gfx_handle != (uint64_t)&intel_ipts) {
DRM_ERROR("invalid gfx handle\n");
return -EINVAL;
}
if (!obj->gem_obj->phys_handle)
ipts_object_unmap(obj);
ipts_object_unpin(obj);
ipts_object_free(obj);
return 0;
}
int intel_ipts_connect(intel_ipts_connect_t *ipts_connect)
{
int ret = 0;
if (!intel_ipts.initialized)
return -EIO;
if (ipts_connect && ipts_connect->if_version <=
SUPPORTED_IPTS_INTERFACE_VERSION) {
/* return gpu operations for ipts */
ipts_connect->ipts_ops.get_wq_info = intel_ipts_get_wq_info;
ipts_connect->ipts_ops.map_buffer = intel_ipts_map_buffer;
ipts_connect->ipts_ops.unmap_buffer = intel_ipts_unmap_buffer;
ipts_connect->gfx_version = INTEL_INFO(intel_ipts.dev)->gen;
ipts_connect->gfx_handle = (uint64_t)&intel_ipts;
/* save callback and data */
intel_ipts.data = ipts_connect->data;
intel_ipts.ipts_clbks = ipts_connect->ipts_cb;
intel_ipts.connected = true;
} else {
ret = -EINVAL;
}
return ret;
}
EXPORT_SYMBOL_GPL(intel_ipts_connect);
void intel_ipts_disconnect(uint64_t gfx_handle)
{
if (!intel_ipts.initialized)
return;
if (gfx_handle != (uint64_t)&intel_ipts ||
intel_ipts.connected == false) {
DRM_ERROR("invalid gfx handle\n");
return;
}
intel_ipts.data = 0;
memset(&intel_ipts.ipts_clbks, 0, sizeof(intel_ipts_callback_t));
intel_ipts.connected = false;
}
EXPORT_SYMBOL_GPL(intel_ipts_disconnect);
static void reacquire_db_work_func(struct work_struct *work)
{
struct delayed_work *d_work = container_of(work, struct delayed_work,
work);
intel_ipts_t *intel_ipts_p = container_of(d_work, intel_ipts_t,
reacquire_db_work);
u32 head;
u32 tail;
u32 size;
u32 load;
head = *(u32*)intel_ipts_p->wq_info.wq_head_addr;
tail = *(u32*)intel_ipts_p->wq_info.wq_tail_addr;
size = intel_ipts_p->wq_info.wq_size;
if (head >= tail)
load = head - tail;
else
load = head + size - tail;
if (load < REACQUIRE_DB_THRESHOLD) {
intel_ipts_p->need_reacquire_db = false;
goto reschedule_work;
}
if (intel_ipts_p->need_reacquire_db) {
if (intel_ipts_p->old_head == head && intel_ipts_p->old_tail == tail)
intel_ipts_reacquire_db(intel_ipts_p);
intel_ipts_p->need_reacquire_db = false;
} else {
intel_ipts_p->old_head = head;
intel_ipts_p->old_tail = tail;
intel_ipts_p->need_reacquire_db = true;
/* recheck */
schedule_delayed_work(&intel_ipts_p->reacquire_db_work,
msecs_to_jiffies(DB_LOST_CHECK_STEP2_INTERVAL));
return;
}
reschedule_work:
schedule_delayed_work(&intel_ipts_p->reacquire_db_work,
msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL));
}
/**
* intel_ipts_init - Initialize ipts support
* @dev: drm device
*
* Setup the required structures for ipts.
*/
int intel_ipts_init(struct drm_device *dev)
{
int ret = 0;
intel_ipts.dev = dev;
INIT_DELAYED_WORK(&intel_ipts.reacquire_db_work, reacquire_db_work_func);
ret = create_ipts_context();
if (ret)
return -ENOMEM;
ret = intel_ipts_init_wq();
if (ret)
return ret;
intel_ipts.initialized = true;
DRM_DEBUG_DRIVER("Intel iTouch framework initialized\n");
return ret;
}
void intel_ipts_cleanup(struct drm_device *dev)
{
intel_ipts_object_t *obj, *n;
if (intel_ipts.dev == dev) {
list_for_each_entry_safe(obj, n, &intel_ipts.buffers.list, list) {
list_del(&obj->list);
if (!obj->gem_obj->phys_handle)
ipts_object_unmap(obj);
ipts_object_unpin(obj);
i915_gem_free_object(&obj->gem_obj->base);
kfree(obj);
}
intel_ipts_release_wq();
destroy_ipts_context();
cancel_delayed_work(&intel_ipts.reacquire_db_work);
}
}