#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"
/* Workqueue to handle Greybus operation completions. */
static struct workqueue_struct *gb_operation_workqueue;
+/* Wait queue for synchronous cancellations. */
+static DECLARE_WAIT_QUEUE_HEAD(gb_operation_cancellation_queue);
+
/*
* Protects access to connection operations lists, as well as
* updates to operation->errno.
/* Caller holds operation reference. */
static inline void gb_operation_put_active(struct gb_operation *operation)
{
- atomic_dec(&operation->active);
+ if (atomic_dec_and_test(&operation->active)) {
+ if (atomic_read(&operation->waiters))
+ wake_up(&gb_operation_cancellation_queue);
+ }
+}
+
+static inline bool gb_operation_is_active(struct gb_operation *operation)
+{
+ return atomic_read(&operation->active);
}
/*
init_completion(&operation->completion);
kref_init(&operation->kref);
atomic_set(&operation->active, 0);
+ atomic_set(&operation->waiters, 0);
spin_lock_irqsave(&gb_operations_lock, flags);
list_add_tail(&operation->links, &connection->operations);
}
/*
- * Cancel an operation, and record the given error to indicate why.
+ * Cancel an operation synchronously, and record the given error to indicate
+ * why.
*/
void gb_operation_cancel(struct gb_operation *operation, int errno)
{
gb_operation_put(operation);
}
}
+
+ atomic_inc(&operation->waiters);
+ wait_event(gb_operation_cancellation_queue,
+ !gb_operation_is_active(operation));
+ atomic_dec(&operation->waiters);
}
EXPORT_SYMBOL_GPL(gb_operation_cancel);