[PATCH v3 1/4] staging: greybus: operation: add generic asynchronous timeout operation support

Bryan O'Donoghue pure.logic at nexus-software.ie
Wed Jan 4 00:11:18 UTC 2017


This patch adds a generic mechanism for handling timeouts of asynchronous
operations to operation.c. After doing a gb_operation_request_send() it
schedules a delayed worker. When the delayed worker's timer expires the
worker runs and does a gb_operation_cancel(). A gb_operation_cancel()
operation will result in the operation completion callback being called
with -ETIMEDOUT as the result code. If an operation completion has already
taken place then the gb_operation_cancel() has no effect. This patch means
that each driver doing asynchronous operations doesn't have to have its own
special timeout mechanisms, it just waits for operation.c to provide it
with the appropriate status code for a given operation.

Signed-off-by: Bryan O'Donoghue <pure.logic at nexus-software.ie>
---
 drivers/staging/greybus/operation.c | 66 +++++++++++++++++++++++++++++++++++++
 drivers/staging/greybus/operation.h |  5 +++
 2 files changed, 71 insertions(+)

diff --git a/drivers/staging/greybus/operation.c b/drivers/staging/greybus/operation.c
index 0123109..d258c94 100644
--- a/drivers/staging/greybus/operation.c
+++ b/drivers/staging/greybus/operation.c
@@ -285,6 +285,31 @@ static void gb_operation_work(struct work_struct *work)
 	gb_operation_put(operation);
 }
 
+/*
+ * This function runs once the delayed_work associated with an operation
+ * that was originally launched with gb_operation_send_timeout has expired.
+ *
+ * If the timeout is the first thing to run then this callback cancels the
+ * operation and sets the result-code of the operation to -ETIMEDOUT.
+ * Setting the result-code to -ETIMEDOUT will subsequently cause the
+ * completion callback of the operation to run with -ETIMEDOUT as the result
+ * code.
+ *
+ * Alternatively if the operation has already had it's status code set to
+ * something other than -EINPROGRESS then this function will result in no
+ * further action being taken.
+ *
+ */
+static void gb_operation_delayed_work(struct work_struct *work)
+{
+	struct delayed_work *delayed_work = to_delayed_work(work);
+	struct gb_operation *operation =
+		container_of(delayed_work, struct gb_operation, delayed_work);
+
+	gb_operation_cancel(operation, -ETIMEDOUT);
+	gb_operation_put(operation);
+}
+
 static void gb_operation_message_init(struct gb_host_device *hd,
 				struct gb_message *message, u16 operation_id,
 				size_t payload_size, u8 type)
@@ -524,6 +549,7 @@ gb_operation_create_common(struct gb_connection *connection, u8 type,
 	operation->type = type;
 	operation->errno = -EBADR;  /* Initial value--means "never set" */
 
+	INIT_DELAYED_WORK(&operation->delayed_work, gb_operation_delayed_work);
 	INIT_WORK(&operation->work, gb_operation_work);
 	init_completion(&operation->completion);
 	kref_init(&operation->kref);
@@ -673,6 +699,7 @@ EXPORT_SYMBOL_GPL(gb_operation_put);
 static void gb_operation_sync_callback(struct gb_operation *operation)
 {
 	complete(&operation->completion);
+	cancel_delayed_work_sync(&operation->delayed_work);
 }
 
 /**
@@ -754,6 +781,45 @@ int gb_operation_request_send(struct gb_operation *operation,
 EXPORT_SYMBOL_GPL(gb_operation_request_send);
 
 /*
+ * Send an asynchronous operation. This function will not block, it returns
+ * immediately. The delayed worker gb_operation_delayed_work() will run
+ * unconditionally dropping the extra reference we take below.
+ */
+int gb_operation_request_send_timeout(struct gb_operation *operation,
+				      unsigned int timeout,
+				      gb_operation_callback callback,
+				      gfp_t gfp)
+{
+	bool queued;
+	unsigned long timeout_jiffies;
+	int ret = 0;
+
+	/* Reference dropped later in gb_operation_delayed_work() or on error */
+	gb_operation_get(operation);
+
+	ret = gb_operation_request_send(operation, callback, gfp);
+	if (ret) {
+		gb_operation_put(operation);
+		return ret;
+	}
+
+	if (timeout)
+		timeout_jiffies = msecs_to_jiffies(timeout);
+	else
+		timeout_jiffies = MAX_SCHEDULE_TIMEOUT;
+
+	queued = queue_delayed_work(gb_operation_completion_wq,
+				    &operation->delayed_work,
+				    timeout_jiffies);
+	if (!queued) {
+		gb_operation_put(operation);
+		ret = -EBUSY;
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_operation_request_send_timeout);
+
+/*
  * Send a synchronous operation.  This function is expected to
  * block, returning only when the response has arrived, (or when an
  * error is detected.  The return value is the result of the
diff --git a/drivers/staging/greybus/operation.h b/drivers/staging/greybus/operation.h
index de09a2c..b8c0ba1 100644
--- a/drivers/staging/greybus/operation.h
+++ b/drivers/staging/greybus/operation.h
@@ -98,6 +98,7 @@ struct gb_operation {
 	struct work_struct	work;
 	gb_operation_callback	callback;
 	struct completion	completion;
+	struct delayed_work	delayed_work;
 
 	struct kref		kref;
 	atomic_t		waiters;
@@ -174,6 +175,10 @@ gb_operation_request_send_sync(struct gb_operation *operation)
 			GB_OPERATION_TIMEOUT_DEFAULT);
 }
 
+int gb_operation_request_send_timeout(struct gb_operation *operation,
+				      unsigned int timeout,
+				      gb_operation_callback callback,
+				      gfp_t gfp);
 void gb_operation_cancel(struct gb_operation *operation, int errno);
 void gb_operation_cancel_incoming(struct gb_operation *operation, int errno);
 
-- 
2.7.4



More information about the devel mailing list