diff --git a/drivers/staging/greybus/connection.c b/drivers/staging/greybus/connection.c index 65a2bd5891d9..b9f9b11b1b65 100644 --- a/drivers/staging/greybus/connection.c +++ b/drivers/staging/greybus/connection.c @@ -227,7 +227,6 @@ void gb_connection_destroy(struct gb_connection *connection) if (WARN_ON(!connection)) return; - /* XXX Need to wait for any outstanding requests to complete */ if (WARN_ON(!list_empty(&connection->operations))) { list_for_each_entry_safe(operation, next, &connection->operations, links) diff --git a/drivers/staging/greybus/operation.c b/drivers/staging/greybus/operation.c index abe44c18fb9e..5e8ea0289053 100644 --- a/drivers/staging/greybus/operation.c +++ b/drivers/staging/greybus/operation.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include "greybus.h" @@ -23,6 +25,9 @@ static struct kmem_cache *gb_message_cache; /* 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. @@ -41,7 +46,15 @@ static inline void gb_operation_get_active(struct gb_operation *operation) /* 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); } /* @@ -463,6 +476,7 @@ gb_operation_create_common(struct gb_connection *connection, u8 type, 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); @@ -873,7 +887,8 @@ void gb_connection_recv(struct gb_connection *connection, } /* - * 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) { @@ -890,6 +905,11 @@ 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); diff --git a/drivers/staging/greybus/operation.h b/drivers/staging/greybus/operation.h index b32386636f6e..c8aaf90a006a 100644 --- a/drivers/staging/greybus/operation.h +++ b/drivers/staging/greybus/operation.h @@ -128,6 +128,7 @@ struct gb_operation { struct kref kref; atomic_t active; + atomic_t waiters; struct list_head links; /* connection->operations */ };