// SPDX-License-Identifier: GPL-2.0
/*
* Greybus operations
*
* Copyright 2014-2015 Google Inc.
* Copyright 2014-2015 Linaro Ltd.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include "greybus.h"
#include "greybus_trace.h"
static struct kmem_cache *gb_operation_cache;
static struct kmem_cache *gb_message_cache;
/* Workqueue to handle Greybus operation completions. */
static struct workqueue_struct *gb_operation_completion_wq;
/* Wait queue for synchronous cancellations. */
static DECLARE_WAIT_QUEUE_HEAD(gb_operation_cancellation_queue);
/*
* Protects updates to operation->errno.
*/
static DEFINE_SPINLOCK(gb_operations_lock);
static int gb_operation_response_send(struct gb_operation *operation,
int errno);
/*
* Increment operation active count and add to connection list unless the
* connection is going away.
*
* Caller holds operation reference.
*/
static int gb_operation_get_active(struct gb_operation *operation)
{
struct gb_connection *connection = operation->connection;
unsigned long flags;
spin_lock_irqsave(&connection->lock, flags);
switch (connection->state) {
case GB_CONNECTION_STATE_ENABLED:
break;
case GB_CONNECTION_STATE_ENABLED_TX:
if (gb_operation_is_incoming(operation))
goto err_unlock;
break;
case GB_CONNECTION_STATE_DISCONNECTING:
if (!gb_operation_is_core(operation))
goto err_unlock;
break;
default:
goto err_unlock;
}
if (operation->active++ == 0)
list_add_tail(&operation->links, &connection->operations);
trace_gb_operation_get_active(operation);
spin_unlock_irqrestore(&connection->lock, flags);
return 0;
err_unlock:
spin_unlock_irqrestore(&connection->lock, flags);
return -ENOTCONN;
}
/* Caller holds operation reference. */
static void gb_operation_put_active(struct gb_operation *operation)
{
struct gb_connection *connection = operation->connection;
unsigned long flags;
spin_lock_irqsave(&connection->lock, flags);
trace_gb_operation_put_active(operation);
if (--operation->active == 0) {
list_del(&operation->links);
if (atomic_read(&operation->waiters))
wake_up(&gb_operation_cancellation_queue);
}
spin_unlock_irqrestore(&connection->lock, flags);
}
static bool gb_operation_is_active(struct gb_operation *operation)
{
struct gb_connection *connection = operation->connection;
unsigned long flags;
bool ret;
spin_lock_irqsave(&connection->lock, flags);
ret = operation->active;
spin_unlock_irqrestore(&