Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 12927835 authored by Bryan O'Donoghue's avatar Bryan O'Donoghue Committed by Greg Kroah-Hartman
Browse files

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: default avatarBryan O'Donoghue <bryan.odonoghue@linaro.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 2e238d71
Loading
Loading
Loading
Loading
+369 −19
Original line number Diff line number Diff line
@@ -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 @@ static int gb_loopback_operation_sync(struct gb_loopback *gb, int type,
	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 @@ static int gb_loopback_transfer(struct gb_loopback *gb, u32 len)
	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 (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_ping(gb);
				error = gb_loopback_sync_ping(gb);
			else if (type == GB_LOOPBACK_TYPE_TRANSFER)
			error = gb_loopback_transfer(gb, size);
				error = gb_loopback_sync_transfer(gb, size);
			else if (type == GB_LOOPBACK_TYPE_SINK)
			error = gb_loopback_sink(gb, size);
				error = gb_loopback_sync_sink(gb, size);

			if (error)
				gb->error++;

		gb_loopback_calculate_stats(gb);
			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);