aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/greybus/loopback.c
diff options
context:
space:
mode:
authorBryan O'Donoghue <bryan.odonoghue@linaro.org>2015-12-07 01:59:06 +0000
committerGreg Kroah-Hartman <gregkh@google.com>2015-12-07 14:32:20 -0500
commit12927835d21127d7e528b9ed56fc334ac96db985 (patch)
treeab51bc9aa71514ed1be3edd06b82d3a36fb70ad6 /drivers/staging/greybus/loopback.c
parentgreybus: loopback: Convert cross-thread mutex to spinlock while relaxing connect locks (diff)
downloadlinux-dev-12927835d21127d7e528b9ed56fc334ac96db985.tar.xz
linux-dev-12927835d21127d7e528b9ed56fc334ac96db985.zip
greybus: loopback: Add asynchronous bi-directional support
A particular ask from the firmware people for some time now has been the ability to drive multiple outstanding bi-directional operations from loopback to loopback Interfaces. This patch implments that change. The approach taken is to make a call to gb_operation_send() and have loopback capture the completion callback itself, with a parallel timer to timeout completion callbacks that take too long. The calling thread will issue each gb_operation_send() as fast as it can within the constraints of thread-safety. In order to support this addition the following new sysfs entries are created on a per-connection basis. - async Zero indicates loopback should use the traditional synchronous model i.e. gb_operation_request_send_sync(). Non-zero indicates loopback should use the new asynchronous model i.e. gb_operation_send() - requests_completed This value indicates the number of requests successfully completed. - requests_timedout This value indicates the number of requests which timed out. - timeout The number of microseconds to give an individual asynchronous request before timing that request out. - timeout_min Read-only attribute informs user-space of the minimum allowed timeout. - timeout_max Read-only attribute informs user-space of the maximum allowed timeout. Note requests_completed + requests_timedout should always equal iteration_max, once iteration_count == iteration_max. Also, at this time we support either synchronous or asynchronous operations in one set of transactions. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Diffstat (limited to 'drivers/staging/greybus/loopback.c')
-rw-r--r--drivers/staging/greybus/loopback.c388
1 files changed, 369 insertions, 19 deletions
diff --git a/drivers/staging/greybus/loopback.c b/drivers/staging/greybus/loopback.c
index b65e3e591105..392f9854ff56 100644
--- a/drivers/staging/greybus/loopback.c
+++ b/drivers/staging/greybus/loopback.c
@@ -20,6 +20,7 @@
#include <linux/debugfs.h>
#include <linux/list_sort.h>
#include <linux/spinlock.h>
+#include <linux/workqueue.h>
#include <asm/div64.h>
@@ -43,10 +44,24 @@ struct gb_loopback_device {
/* We need to take a lock in atomic context */
spinlock_t lock;
struct list_head list;
+ struct list_head list_op_async;
+ wait_queue_head_t wq;
};
static struct gb_loopback_device gb_dev;
+struct gb_loopback_async_operation {
+ struct gb_loopback *gb;
+ struct gb_operation *operation;
+ struct timeval ts;
+ struct timer_list timer;
+ struct list_head entry;
+ struct work_struct work;
+ struct kref kref;
+ bool pending;
+ int (*completion)(struct gb_loopback_async_operation *op_async);
+};
+
struct gb_loopback {
struct gb_connection *connection;
@@ -66,18 +81,29 @@ struct gb_loopback {
struct gb_loopback_stats gpbridge_firmware_latency;
int type;
+ int async;
u32 mask;
u32 size;
u32 iteration_max;
u32 iteration_count;
int ms_wait;
u32 error;
+ u32 requests_completed;
+ u32 requests_timedout;
+ u32 timeout;
+ u32 jiffy_timeout;
+ u32 timeout_min;
+ u32 timeout_max;
u32 lbid;
u64 elapsed_nsecs;
u32 apbridge_latency_ts;
u32 gpbridge_latency_ts;
};
+/* Min/max values in jiffies */
+#define GB_LOOPBACK_TIMEOUT_MIN 1
+#define GB_LOOPBACK_TIMEOUT_MAX 10000
+
#define GB_LOOPBACK_FIFO_DEFAULT 8192
static unsigned kfifo_depth = GB_LOOPBACK_FIFO_DEFAULT;
@@ -215,6 +241,8 @@ static void gb_loopback_check_attr(struct gb_loopback *gb,
gb->ms_wait = GB_LOOPBACK_MS_WAIT_MAX;
if (gb->size > gb_dev.size_max)
gb->size = gb_dev.size_max;
+ gb->requests_timedout = 0;
+ gb->requests_completed = 0;
gb->iteration_count = 0;
gb->error = 0;
@@ -230,6 +258,11 @@ static void gb_loopback_check_attr(struct gb_loopback *gb,
case GB_LOOPBACK_TYPE_PING:
case GB_LOOPBACK_TYPE_TRANSFER:
case GB_LOOPBACK_TYPE_SINK:
+ gb->jiffy_timeout = usecs_to_jiffies(gb->timeout);
+ if (!gb->jiffy_timeout)
+ gb->jiffy_timeout = GB_LOOPBACK_TIMEOUT_MIN;
+ else if (gb->jiffy_timeout > GB_LOOPBACK_TIMEOUT_MAX)
+ gb->jiffy_timeout = GB_LOOPBACK_TIMEOUT_MAX;
gb_loopback_reset_stats(gb);
wake_up(&gb->wq);
break;
@@ -252,6 +285,14 @@ gb_loopback_stats_attrs(gpbridge_firmware_latency);
/* Number of errors encountered during loop */
gb_loopback_ro_attr(error);
+/* Number of requests successfully completed async */
+gb_loopback_ro_attr(requests_completed);
+/* Number of requests timed out async */
+gb_loopback_ro_attr(requests_timedout);
+/* Timeout minimum in useconds */
+gb_loopback_ro_attr(timeout_min);
+/* Timeout minimum in useconds */
+gb_loopback_ro_attr(timeout_max);
/*
* Type of loopback message to send based on protocol type definitions
@@ -270,8 +311,12 @@ gb_dev_loopback_rw_attr(ms_wait, d);
gb_dev_loopback_rw_attr(iteration_max, u);
/* The current index of the for (i = 0; i < iteration_max; i++) loop */
gb_dev_loopback_ro_attr(iteration_count, false);
-/* A bit-mask of destination connecitons to include in the test run */
+/* A bit-mask of destination connections to include in the test run */
gb_dev_loopback_rw_attr(mask, u);
+/* A flag to indicate synchronous or asynchronous operations */
+gb_dev_loopback_rw_attr(async, u);
+/* Timeout of an individual asynchronous request */
+gb_dev_loopback_rw_attr(timeout, u);
static struct attribute *loopback_attrs[] = {
&dev_attr_latency_min.attr,
@@ -295,11 +340,19 @@ static struct attribute *loopback_attrs[] = {
&dev_attr_iteration_count.attr,
&dev_attr_iteration_max.attr,
&dev_attr_mask.attr,
+ &dev_attr_async.attr,
&dev_attr_error.attr,
+ &dev_attr_requests_completed.attr,
+ &dev_attr_requests_timedout.attr,
+ &dev_attr_timeout.attr,
+ &dev_attr_timeout_min.attr,
+ &dev_attr_timeout_max.attr,
NULL,
};
ATTRIBUTE_GROUPS(loopback);
+static void gb_loopback_calculate_stats(struct gb_loopback *gb);
+
static u32 gb_loopback_nsec_to_usec_latency(u64 elapsed_nsecs)
{
u32 lat;
@@ -381,7 +434,200 @@ error:
return ret;
}
-static int gb_loopback_sink(struct gb_loopback *gb, u32 len)
+static void __gb_loopback_async_operation_destroy(struct kref *kref)
+{
+ struct gb_loopback_async_operation *op_async;
+
+ op_async = container_of(kref, struct gb_loopback_async_operation, kref);
+
+ list_del(&op_async->entry);
+ if (op_async->operation)
+ gb_operation_put(op_async->operation);
+ kfree(op_async);
+}
+
+static void gb_loopback_async_operation_get(struct gb_loopback_async_operation
+ *op_async)
+{
+ kref_get(&op_async->kref);
+}
+
+static void gb_loopback_async_operation_put(struct gb_loopback_async_operation
+ *op_async)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gb_dev.lock, flags);
+ kref_put(&op_async->kref, __gb_loopback_async_operation_destroy);
+ spin_unlock_irqrestore(&gb_dev.lock, flags);
+}
+
+static struct gb_loopback_async_operation *
+ gb_loopback_operation_find(u16 id)
+{
+ struct gb_loopback_async_operation *op_async;
+ bool found = false;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gb_dev.lock, flags);
+ list_for_each_entry(op_async, &gb_dev.list_op_async, entry) {
+ if (op_async->operation->id == id) {
+ gb_loopback_async_operation_get(op_async);
+ found = true;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&gb_dev.lock, flags);
+
+ return found ? op_async : NULL;
+}
+
+static void gb_loopback_async_operation_callback(struct gb_operation *operation)
+{
+ struct gb_loopback_async_operation *op_async;
+ struct gb_loopback *gb;
+ struct timeval te;
+ bool err = false;
+
+ do_gettimeofday(&te);
+ op_async = gb_loopback_operation_find(operation->id);
+ if (!op_async)
+ return;
+
+ gb = op_async->gb;
+ mutex_lock(&gb->mutex);
+
+ if (!op_async->pending || gb_operation_result(operation)) {
+ err = true;
+ } else {
+ if (op_async->completion)
+ if (op_async->completion(op_async))
+ err = true;
+ }
+
+ if (err) {
+ gb->error++;
+ } else {
+ gb->requests_completed++;
+ gb_loopback_push_latency_ts(gb, &op_async->ts, &te);
+ gb->elapsed_nsecs = gb_loopback_calc_latency(&op_async->ts,
+ &te);
+ gb_loopback_calculate_stats(gb);
+ }
+
+ if (op_async->pending) {
+ gb->iteration_count++;
+ op_async->pending = false;
+ del_timer_sync(&op_async->timer);
+ gb_loopback_async_operation_put(op_async);
+ }
+ mutex_unlock(&gb->mutex);
+
+ dev_dbg(&gb->connection->bundle->dev, "complete operation %d\n",
+ operation->id);
+
+ gb_loopback_async_operation_put(op_async);
+}
+
+static void gb_loopback_async_operation_work(struct work_struct *work)
+{
+ struct gb_loopback *gb;
+ struct gb_operation *operation;
+ struct gb_loopback_async_operation *op_async;
+
+ op_async = container_of(work, struct gb_loopback_async_operation, work);
+ if (!op_async)
+ return;
+
+ gb = op_async->gb;
+ operation = op_async->operation;
+
+ mutex_lock(&gb->mutex);
+ if (op_async->pending) {
+ gb->requests_timedout++;
+ gb->error++;
+ gb->iteration_count++;
+ op_async->pending = false;
+ gb_loopback_async_operation_put(op_async);
+ }
+ mutex_unlock(&gb->mutex);
+
+ dev_dbg(&gb->connection->bundle->dev, "timeout operation %d\n",
+ operation->id);
+
+ gb_operation_cancel(operation, -ETIMEDOUT);
+ gb_loopback_async_operation_put(op_async);
+}
+
+static void gb_loopback_async_operation_timeout(unsigned long data)
+{
+ struct gb_loopback_async_operation *op_async;
+ u16 id = data;
+
+ op_async = gb_loopback_operation_find(id);
+ if (!op_async) {
+ pr_err("operation %d not found - time out ?\n", id);
+ return;
+ }
+ schedule_work(&op_async->work);
+}
+
+static int gb_loopback_async_operation(struct gb_loopback *gb, int type,
+ void *request, int request_size,
+ int response_size,
+ void *completion)
+{
+ struct gb_loopback_async_operation *op_async;
+ struct gb_operation *operation;
+ int ret;
+ unsigned long flags;
+
+ op_async = kzalloc(sizeof(*op_async), GFP_KERNEL);
+ if (!op_async)
+ return -ENOMEM;
+
+ INIT_WORK(&op_async->work, gb_loopback_async_operation_work);
+ init_timer(&op_async->timer);
+ kref_init(&op_async->kref);
+
+ operation = gb_operation_create(gb->connection, type, request_size,
+ response_size, GFP_KERNEL);
+ if (!operation) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (request_size)
+ memcpy(operation->request->payload, request, request_size);
+
+ op_async->gb = gb;
+ op_async->operation = operation;
+ op_async->completion = completion;
+
+ spin_lock_irqsave(&gb_dev.lock, flags);
+ list_add_tail(&op_async->entry, &gb_dev.list_op_async);
+ spin_unlock_irqrestore(&gb_dev.lock, flags);
+
+ do_gettimeofday(&op_async->ts);
+ op_async->pending = true;
+ ret = gb_operation_request_send(operation,
+ gb_loopback_async_operation_callback,
+ GFP_KERNEL);
+ if (ret)
+ goto error;
+
+ op_async->timer.function = gb_loopback_async_operation_timeout;
+ op_async->timer.expires = jiffies + gb->jiffy_timeout;
+ op_async->timer.data = (unsigned long)operation->id;
+ add_timer(&op_async->timer);
+
+ return ret;
+error:
+ gb_loopback_async_operation_put(op_async);
+ return ret;
+}
+
+static int gb_loopback_sync_sink(struct gb_loopback *gb, u32 len)
{
struct gb_loopback_transfer_request *request;
int retval;
@@ -398,7 +644,7 @@ static int gb_loopback_sink(struct gb_loopback *gb, u32 len)
return retval;
}
-static int gb_loopback_transfer(struct gb_loopback *gb, u32 len)
+static int gb_loopback_sync_transfer(struct gb_loopback *gb, u32 len)
{
struct gb_loopback_transfer_request *request;
struct gb_loopback_transfer_response *response;
@@ -440,12 +686,91 @@ gb_error:
return retval;
}
-static int gb_loopback_ping(struct gb_loopback *gb)
+static int gb_loopback_sync_ping(struct gb_loopback *gb)
{
return gb_loopback_operation_sync(gb, GB_LOOPBACK_TYPE_PING,
NULL, 0, NULL, 0);
}
+static int gb_loopback_async_sink(struct gb_loopback *gb, u32 len)
+{
+ struct gb_loopback_transfer_request *request;
+ int retval;
+
+ request = kmalloc(len + sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->len = cpu_to_le32(len);
+ retval = gb_loopback_async_operation(gb, GB_LOOPBACK_TYPE_SINK,
+ request, len + sizeof(*request),
+ 0, NULL);
+ kfree(request);
+ return retval;
+}
+
+static int gb_loopback_async_transfer_complete(
+ struct gb_loopback_async_operation *op_async)
+{
+ struct gb_loopback *gb;
+ struct gb_operation *operation;
+ struct gb_loopback_transfer_request *request;
+ struct gb_loopback_transfer_response *response;
+ size_t len;
+ int retval = 0;
+
+ gb = op_async->gb;
+ operation = op_async->operation;
+ request = operation->request->payload;
+ response = operation->response->payload;
+ len = le32_to_cpu(request->len);
+
+ if (memcmp(request->data, response->data, len)) {
+ dev_err(&gb->connection->bundle->dev,
+ "Loopback Data doesn't match operation id %d\n",
+ operation->id);
+ retval = -EREMOTEIO;
+ } else {
+ gb->apbridge_latency_ts =
+ (u32)__le32_to_cpu(response->reserved0);
+ gb->gpbridge_latency_ts =
+ (u32)__le32_to_cpu(response->reserved1);
+ }
+
+ return retval;
+}
+
+static int gb_loopback_async_transfer(struct gb_loopback *gb, u32 len)
+{
+ struct gb_loopback_transfer_request *request;
+ int retval, response_len;
+
+ request = kmalloc(len + sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ memset(request->data, 0x5A, len);
+
+ request->len = cpu_to_le32(len);
+ response_len = sizeof(struct gb_loopback_transfer_response);
+ retval = gb_loopback_async_operation(gb, GB_LOOPBACK_TYPE_TRANSFER,
+ request, len + sizeof(*request),
+ len + response_len,
+ gb_loopback_async_transfer_complete);
+ if (retval)
+ goto gb_error;
+
+gb_error:
+ kfree(request);
+ return retval;
+}
+
+static int gb_loopback_async_ping(struct gb_loopback *gb)
+{
+ return gb_loopback_async_operation(gb, GB_LOOPBACK_TYPE_PING,
+ NULL, 0, 0, NULL);
+}
+
static int gb_loopback_request_recv(u8 type, struct gb_operation *operation)
{
struct gb_connection *connection = operation->connection;
@@ -512,6 +837,10 @@ static void gb_loopback_reset_stats(struct gb_loopback *gb)
memcpy(&gb->gpbridge_firmware_latency, &reset,
sizeof(struct gb_loopback_stats));
+ /* Set values to report min/max timeout to user-space */
+ gb->timeout_min = jiffies_to_usecs(GB_LOOPBACK_TIMEOUT_MIN);
+ gb->timeout_max = jiffies_to_usecs(GB_LOOPBACK_TIMEOUT_MAX);
+
/* Reset aggregate stats */
memcpy(&gb->latency, &reset, sizeof(struct gb_loopback_stats));
memcpy(&gb->throughput, &reset, sizeof(struct gb_loopback_stats));
@@ -599,23 +928,25 @@ static int gb_loopback_fn(void *data)
int ms_wait = 0;
int type;
u32 size;
+ u32 send_count = 0;
struct gb_loopback *gb = data;
while (1) {
if (!gb->type)
wait_event_interruptible(gb->wq, gb->type ||
kthread_should_stop());
+
if (kthread_should_stop())
break;
mutex_lock(&gb->mutex);
-
sysfs_notify(&gb->connection->bundle->dev.kobj,
NULL, "iteration_count");
/* Optionally terminate */
- if (gb->iteration_count == gb->iteration_max) {
+ if (send_count == gb->iteration_max) {
gb->type = 0;
+ send_count = 0;
mutex_unlock(&gb->mutex);
continue;
}
@@ -625,19 +956,33 @@ static int gb_loopback_fn(void *data)
mutex_unlock(&gb->mutex);
/* Else operations to perform */
- if (type == GB_LOOPBACK_TYPE_PING)
- error = gb_loopback_ping(gb);
- else if (type == GB_LOOPBACK_TYPE_TRANSFER)
- error = gb_loopback_transfer(gb, size);
- else if (type == GB_LOOPBACK_TYPE_SINK)
- error = gb_loopback_sink(gb, size);
-
- if (error)
- gb->error++;
-
- gb_loopback_calculate_stats(gb);
- gb->iteration_count++;
-
+ if (gb->async) {
+ if (type == GB_LOOPBACK_TYPE_PING) {
+ error = gb_loopback_async_ping(gb);
+ gb_loopback_calculate_stats(gb);
+ } else if (type == GB_LOOPBACK_TYPE_TRANSFER) {
+ error = gb_loopback_async_transfer(gb, size);
+ } else if (type == GB_LOOPBACK_TYPE_SINK) {
+ error = gb_loopback_async_sink(gb, size);
+ }
+
+ if (error)
+ gb->error++;
+ } else {
+ /* We are effectively single threaded here */
+ if (type == GB_LOOPBACK_TYPE_PING)
+ error = gb_loopback_sync_ping(gb);
+ else if (type == GB_LOOPBACK_TYPE_TRANSFER)
+ error = gb_loopback_sync_transfer(gb, size);
+ else if (type == GB_LOOPBACK_TYPE_SINK)
+ error = gb_loopback_sync_sink(gb, size);
+
+ if (error)
+ gb->error++;
+ gb->iteration_count++;
+ gb_loopback_calculate_stats(gb);
+ }
+ send_count++;
if (ms_wait)
msleep(ms_wait);
}
@@ -742,6 +1087,10 @@ static int gb_loopback_connection_init(struct gb_connection *connection)
init_waitqueue_head(&gb->wq);
gb_loopback_reset_stats(gb);
+ /* Reported values to user-space for min/max timeouts */
+ gb->timeout_min = jiffies_to_usecs(GB_LOOPBACK_TIMEOUT_MIN);
+ gb->timeout_max = jiffies_to_usecs(GB_LOOPBACK_TIMEOUT_MAX);
+
if (!gb_dev.count) {
/* Calculate maximum payload */
gb_dev.size_max = gb_operation_get_payload_size_max(connection);
@@ -847,6 +1196,7 @@ static int loopback_init(void)
int retval;
INIT_LIST_HEAD(&gb_dev.list);
+ INIT_LIST_HEAD(&gb_dev.list_op_async);
spin_lock_init(&gb_dev.lock);
gb_dev.root = debugfs_create_dir("gb_loopback", NULL);