blob: a2a3dd0033a88febe783e41154f3da2d7798f7e1 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* GXP client structure.
*
* Copyright (C) 2021 Google LLC
*/
#include <linux/dma-fence-array.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <gcip/gcip-dma-fence.h>
#include <gcip/gcip-pm.h>
#include "gxp-client.h"
#include "gxp-config.h"
#include "gxp-dma.h"
#include "gxp-internal.h"
#include "gxp-pm.h"
#include "gxp-vd.h"
#include "gxp.h"
#if GXP_HAS_MCU
#include "gxp-uci.h"
#endif /* GXP_HAS_MCU */
/**
* uci_cmd_work_func() - The work function to execute the UCI work in the queue.
* @work: The work object embedded in the client.
*
* All the UCI work in the uci_work_list will be removed and sent.
*/
static void uci_cmd_work_func(struct work_struct *work)
{
#if GXP_HAS_MCU
struct gxp_client *client = container_of(work, struct gxp_client, uci_worker);
struct gxp_uci_cmd_work *uci_work, *tmp;
LIST_HEAD(fetched_work);
unsigned long flags;
int ret;
spin_lock_irqsave(&client->uci_work_list_lock, flags);
list_replace_init(&client->uci_work_list, &fetched_work);
spin_unlock_irqrestore(&client->uci_work_list_lock, flags);
list_for_each_entry_safe(uci_work, tmp, &fetched_work, node) {
list_del_init(&uci_work->node);
ret = gxp_uci_create_and_send_cmd(client, uci_work->cmd_seq, uci_work->flags,
uci_work->opaque, uci_work->timeout_ms,
uci_work->in_fences, uci_work->out_fences);
if (ret) {
dev_err(client->gxp->dev,
"Failed to process uci command in work func (ret=%d)", ret);
}
gxp_uci_work_destroy(uci_work);
}
#endif /* GXP_HAS_MCU */
}
struct gxp_client *gxp_client_create(struct gxp_dev *gxp)
{
struct gxp_client *client;
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client)
return ERR_PTR(-ENOMEM);
client->gxp = gxp;
lockdep_register_key(&client->key);
__init_rwsem(&client->semaphore, "&client->semaphore", &client->key);
client->has_block_wakelock = false;
client->has_vd_wakelock = false;
client->requested_states = off_states;
client->vd = NULL;
INIT_WORK(&client->uci_worker, uci_cmd_work_func);
client->uci_cb_disabled = false;
spin_lock_init(&client->uci_cb_list_lock);
INIT_LIST_HEAD(&client->uci_cb_list);
spin_lock_init(&client->uci_work_list_lock);
INIT_LIST_HEAD(&client->uci_work_list);
return client;
}
/**
* cleanup_uci_cmd_work() - Disable UCI work and clean up the remain UCI work from the work list.
* @client: The client to be cleaned up.
*
* Each work in the work list will be removed from the callback list of the fence it added to.
* If the removal failed, that means the fence has been signaled and nothing need to be done.
* At the end of the function, cancel the pending work and wait until the running one finished.
*/
static void cleanup_uci_cmd_work(struct gxp_client *client)
{
#if GXP_HAS_MCU
struct gxp_uci_cmd_work *uci_work, *tmp;
unsigned long flags;
spin_lock_irqsave(&client->uci_cb_list_lock, flags);
client->uci_cb_disabled = true;
spin_unlock_irqrestore(&client->uci_cb_list_lock, flags);
list_for_each_entry_safe(uci_work, tmp, &client->uci_cb_list, node) {
if (dma_fence_remove_callback(uci_work->fence, &uci_work->cb)) {
/*
* If the fence is a fence array created by us, the callbacks of underlying
* fence need to be removed manually.
*/
if (dma_fence_is_array(uci_work->fence) && uci_work->in_fences &&
uci_work->in_fences->size > 1)
gcip_dma_fence_array_disable_signaling(uci_work->fence);
}
list_del(&uci_work->node);
gxp_uci_work_destroy(uci_work);
}
/* Cancel the work and wait for its execution to finish. */
cancel_work_sync(&client->uci_worker);
/* If any work canceled, there could be left over in uci_work_list. */
list_for_each_entry_safe(uci_work, tmp, &client->uci_work_list, node) {
list_del(&uci_work->node);
gxp_uci_work_destroy(uci_work);
}
#endif /* GXP_HAS_MCU */
}
void gxp_client_destroy(struct gxp_client *client)
{
struct gxp_dev *gxp = client->gxp;
int core;
cleanup_uci_cmd_work(client);
down_write(&client->semaphore);
if (client->vd && client->vd->state != GXP_VD_OFF) {
down_write(&gxp->vd_semaphore);
gxp_vd_stop(client->vd);
up_write(&gxp->vd_semaphore);
}
if (client->vd && client->has_block_wakelock) {
down_write(&gxp->vd_semaphore);
gxp_vd_block_unready(client->vd);
up_write(&gxp->vd_semaphore);
}
for (core = 0; core < GXP_NUM_CORES; core++) {
if (client->mb_eventfds[core])
gxp_eventfd_put(client->mb_eventfds[core]);
}
#if HAS_TPU_EXT
if (client->tpu_file) {
if (client->vd) {
if (gxp->before_unmap_tpu_mbx_queue)
gxp->before_unmap_tpu_mbx_queue(gxp, client);
if (gxp_is_direct_mode(gxp))
gxp_dma_unmap_tpu_buffer(gxp,
client->vd->domain,
client->mbx_desc);
}
fput(client->tpu_file);
client->tpu_file = NULL;
}
#endif /* HAS_TPU_EXT */
if (client->vd) {
down_write(&gxp->vd_semaphore);
gxp_vd_release(client->vd);
up_write(&gxp->vd_semaphore);
client->vd = NULL;
}
up_write(&client->semaphore);
/*
* This part should be located outside of the @client->semaphore protection to prevent the
* PM lock being dependent on client->semaphore. A reverse chain already exists inside
* gxp_mcu_firmware_crash_handler().
*
* The protection is not required because the only place that may change states related to
* has_block_wakelock is ioctl(acquire/release wakelock) but as this function is only called
* on releasing client, those ioctls are impossible to be called.
*/
if (client->has_block_wakelock) {
gcip_pm_put(client->gxp->power_mgr->pm);
gxp_pm_update_requested_power_states(gxp, client->requested_states, off_states);
}
lockdep_unregister_key(&client->key);
kfree(client);
}
static int gxp_set_secure_vd(struct gxp_virtual_device *vd)
{
struct gxp_dev *gxp = vd->gxp;
if (gxp_is_direct_mode(gxp))
return 0;
mutex_lock(&gxp->secure_vd_lock);
if (gxp->secure_vd) {
mutex_unlock(&gxp->secure_vd_lock);
return -EEXIST;
}
vd->is_secure = true;
gxp->secure_vd = vd;
mutex_unlock(&gxp->secure_vd_lock);
return 0;
}
int gxp_client_allocate_virtual_device(struct gxp_client *client,
uint core_count, u8 flags)
{
struct gxp_dev *gxp = client->gxp;
struct gxp_virtual_device *vd;
int ret;
lockdep_assert_held(&client->semaphore);
if (client->vd) {
dev_err(gxp->dev,
"Virtual device was already allocated for client\n");
return -EINVAL;
}
down_write(&gxp->vd_semaphore);
vd = gxp_vd_allocate(gxp, core_count);
if (IS_ERR(vd)) {
ret = PTR_ERR(vd);
dev_err(gxp->dev,
"Failed to allocate virtual device for client (%d)\n",
ret);
goto error;
}
if (flags & GXP_ALLOCATE_VD_SECURE) {
ret = gxp_set_secure_vd(vd);
if (ret)
goto error_vd_release;
}
if (client->has_block_wakelock) {
ret = gxp_vd_block_ready(vd);
if (ret)
goto error_vd_release;
}
up_write(&gxp->vd_semaphore);
client->vd = vd;
return 0;
error_vd_release:
gxp_vd_release(vd);
error:
up_write(&gxp->vd_semaphore);
return ret;
}
static int gxp_client_request_power_states(struct gxp_client *client,
struct gxp_power_states requested_states)
{
struct gxp_dev *gxp = client->gxp;
int ret;
if (gxp->request_power_states) {
ret = gxp->request_power_states(client, requested_states);
if (ret != -EOPNOTSUPP)
return ret;
}
gxp_pm_update_requested_power_states(gxp, client->requested_states,
requested_states);
client->requested_states = requested_states;
return 0;
}
int gxp_client_acquire_block_wakelock(struct gxp_client *client,
bool *acquired_wakelock)
{
struct gxp_dev *gxp = client->gxp;
int ret;
lockdep_assert_held(&client->semaphore);
if (!client->has_block_wakelock) {
*acquired_wakelock = true;
if (client->vd) {
down_write(&gxp->vd_semaphore);
ret = gxp_vd_block_ready(client->vd);
up_write(&gxp->vd_semaphore);
if (ret)
goto err_wakelock_release;
}
} else {
*acquired_wakelock = false;
}
client->has_block_wakelock = true;
/*
* Update client's TGID+PID in case the process that opened
* /dev/gxp is not the one that called this IOCTL.
*/
client->tgid = current->tgid;
client->pid = current->pid;
return 0;
err_wakelock_release:
*acquired_wakelock = false;
return ret;
}
bool gxp_client_release_block_wakelock(struct gxp_client *client)
{
struct gxp_dev *gxp = client->gxp;
lockdep_assert_held(&client->semaphore);
if (!client->has_block_wakelock)
return false;
gxp_client_release_vd_wakelock(client);
if (client->vd) {
down_write(&gxp->vd_semaphore);
gxp_vd_block_unready(client->vd);
up_write(&gxp->vd_semaphore);
}
client->has_block_wakelock = false;
return true;
}
int gxp_client_acquire_vd_wakelock(struct gxp_client *client,
struct gxp_power_states requested_states)
{
struct gxp_dev *gxp = client->gxp;
int ret = 0;
enum gxp_virtual_device_state orig_state;
if (!gxp_is_direct_mode(gxp))
return 0;
lockdep_assert_held(&client->semaphore);
if (!client->has_block_wakelock) {
dev_err(gxp->dev,
"Must hold BLOCK wakelock to acquire VIRTUAL_DEVICE wakelock\n");
return -EINVAL;
}
if (client->vd->state == GXP_VD_UNAVAILABLE) {
dev_err(gxp->dev,
"Cannot acquire VIRTUAL_DEVICE wakelock on a broken virtual device\n");
return -ENODEV;
}
if (!client->has_vd_wakelock) {
down_write(&gxp->vd_semaphore);
orig_state = client->vd->state;
if (client->vd->state == GXP_VD_READY || client->vd->state == GXP_VD_OFF)
ret = gxp_vd_run(client->vd);
else
ret = gxp_vd_resume(client->vd);
up_write(&gxp->vd_semaphore);
}
if (ret)
goto out;
ret = gxp_client_request_power_states(client, requested_states);
if (ret)
goto out_release_vd_wakelock;
client->has_vd_wakelock = true;
return 0;
out_release_vd_wakelock:
if (!client->has_vd_wakelock) {
down_write(&gxp->vd_semaphore);
if (orig_state == GXP_VD_READY || orig_state == GXP_VD_OFF)
gxp_vd_stop(client->vd);
else
gxp_vd_suspend(client->vd);
up_write(&gxp->vd_semaphore);
}
out:
return ret;
}
void gxp_client_release_vd_wakelock(struct gxp_client *client)
{
struct gxp_dev *gxp = client->gxp;
if (!gxp_is_direct_mode(gxp))
return;
lockdep_assert_held(&client->semaphore);
if (!client->has_vd_wakelock)
return;
/*
* Currently VD state will not be GXP_VD_UNAVAILABLE if
* has_vd_wakelock is true. Add this check just in case
* GXP_VD_UNAVAILABLE will occur in more scenarios in the
* future.
*/
if (client->vd->state == GXP_VD_UNAVAILABLE)
return;
down_write(&gxp->vd_semaphore);
gxp_vd_suspend(client->vd);
up_write(&gxp->vd_semaphore);
gxp_client_request_power_states(client, off_states);
client->has_vd_wakelock = false;
}
bool gxp_client_has_available_vd(struct gxp_client *client, const char *name)
{
struct gxp_dev *gxp = client->gxp;
lockdep_assert_held(&client->semaphore);
if (!client->vd) {
dev_err(gxp->dev,
"%s requires the client allocate a VIRTUAL_DEVICE\n",
name);
return false;
}
if (client->vd->state == GXP_VD_UNAVAILABLE) {
dev_err(gxp->dev, "Cannot do %s on a broken virtual device\n",
name);
return false;
}
return true;
}