// 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 <linux/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(&connection->lock, flags);
return ret;
}
/*
* Set an operation's result.
*
* Initially an outgoing operation's errno value is -EBADR.
* If no error occurs before sending the request message the only
* valid value operation->errno can be set to is -EINPROGRESS,
* indicating the request has been (or rather is about to be) sent.
* At that point nobody should be looking at the result until the
* response arrives.
*
* The first time the result gets set after the request has been
* sent, that result "sticks." That is, if two concurrent threads
* race to set the result, the first one wins. The return value
* tells the caller whether its result was recorded; if not the
* caller has nothing more to do.
*
* The result value -EILSEQ is reserved to signal an implementation
* error; if it's ever observed, the code performing the request has
* done something fundamentally wrong. It is an error to try to set
* the result to -EBADR, and attempts to do so result in a warning,
* and -EILSEQ is used instead. Similarly, the only valid result
* value to set for an operation in initial state is -EINPROGRESS.
* Attempts to do otherwise will also record a (successful) -EILSEQ
* operation result.
*/
static bool gb_operation_result_set(struct gb_operation *operation, int result)
{
unsigned long flags;
int prev;
if (result == -EINPROGRESS) {
/*