summaryrefslogblamecommitdiffstats
path: root/drivers/staging/greybus/loopback.c
blob: 9914b52c71ceed0c45bb8bf9d2cece41231382ab (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                                          
                        









































































































































































































































































































































                                                                             
                                                                             



























                                                                         
                                                                    















                                                                  
/*
 * Loopback bridge driver for the Greybus loopback module.
 *
 * Copyright 2014 Google Inc.
 * Copyright 2014 Linaro Ltd.
 *
 * Released under the GPLv2 only.
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/random.h>
#include <linux/sizes.h>
#include "greybus.h"

struct gb_loopback_stats {
	u32 min;
	u32 max;
	u32 avg;
	u32 sum;
	u32 count;
};

struct gb_loopback {
	struct gb_connection *connection;
	u8 version_major;
	u8 version_minor;

	struct task_struct *task;

	int type;
	u32 size;
	int ms_wait;

	struct gb_loopback_stats latency;
	struct gb_loopback_stats throughput;
	struct gb_loopback_stats frequency;
	struct timeval ts;
	struct timeval te;
	u64 elapsed_nsecs;
	u32 error;
};

/* Version of the Greybus loopback protocol we support */
#define	GB_LOOPBACK_VERSION_MAJOR			0x00
#define	GB_LOOPBACK_VERSION_MINOR			0x01

/* Greybus loopback request types */
#define	GB_LOOPBACK_TYPE_INVALID			0x00
#define	GB_LOOPBACK_TYPE_PROTOCOL_VERSION		0x01
#define	GB_LOOPBACK_TYPE_PING				0x02
#define	GB_LOOPBACK_TYPE_TRANSFER			0x03

#define GB_LOOPBACK_SIZE_MAX				SZ_4K

/* Define get_version() routine */
define_get_version(gb_loopback, LOOPBACK);

/* interface sysfs attributes */
#define gb_loopback_ro_attr(field, type)				\
static ssize_t field##_show(struct device *dev,				\
			    struct device_attribute *attr,		\
			    char *buf)					\
{									\
	struct gb_connection *connection = to_gb_connection(dev);	\
	struct gb_loopback *gb =					\
		(struct gb_loopback *)connection->private;		\
	return sprintf(buf, "%"#type"\n", gb->field);			\
}									\
static DEVICE_ATTR_RO(field)

#define gb_loopback_ro_stats_attr(name, field, type)			\
static ssize_t name##_##field##_show(struct device *dev,		\
			    struct device_attribute *attr,		\
			    char *buf)					\
{									\
	struct gb_connection *connection = to_gb_connection(dev);	\
	struct gb_loopback *gb =					\
		(struct gb_loopback *)connection->private;		\
	return sprintf(buf, "%"#type"\n", gb->name.field);		\
}									\
static DEVICE_ATTR_RO(name##_##field)

#define gb_loopback_stats_attrs(field)					\
	gb_loopback_ro_stats_attr(field, min, d);			\
	gb_loopback_ro_stats_attr(field, max, d);			\
	gb_loopback_ro_stats_attr(field, avg, d);

#define gb_loopback_attr(field, type)					\
static ssize_t field##_show(struct device *dev,				\
			    struct device_attribute *attr,		\
			    char *buf)					\
{									\
	struct gb_connection *connection = to_gb_connection(dev);	\
	struct gb_loopback *gb =					\
		(struct gb_loopback *)connection->private;		\
	return sprintf(buf, "%"#type"\n", gb->field);			\
}									\
static ssize_t field##_store(struct device *dev,			\
			    struct device_attribute *attr,		\
			    const char *buf,				\
			    size_t len)					\
{									\
	int ret;							\
	struct gb_connection *connection = to_gb_connection(dev);	\
	struct gb_loopback *gb =					\
		(struct gb_loopback *)connection->private;		\
	ret = sscanf(buf, "%"#type, &gb->field);			\
	pr_err("%s = %"#type"\n", #field, gb->field);			\
	if (ret != 1)							\
		return -EINVAL;						\
	gb_loopback_check_attr(gb);					\
	return len;							\
}									\
static DEVICE_ATTR_RW(field)

static void gb_loopback_reset_stats(struct gb_loopback *gb);
static void gb_loopback_check_attr(struct gb_loopback *gb)
{
	if (gb->ms_wait > 1000)
		gb->ms_wait = 1000;
	if (gb->type > 3)
		gb->type = 0;
	if (gb->size > GB_LOOPBACK_SIZE_MAX)
		gb->size = GB_LOOPBACK_SIZE_MAX;
	gb->error = 0;
	gb_loopback_reset_stats(gb);
}

/* Time to send and receive one message */
gb_loopback_stats_attrs(latency);
/* Number of packet sent per second on this cport */
gb_loopback_stats_attrs(frequency);
/* Quantity of data sent and received on this cport */
gb_loopback_stats_attrs(throughput);
gb_loopback_ro_attr(error, d);

/*
 * Type of loopback message to send
 * 0 => Don't send message
 * 1 => Send ping message continuously (message without payload)
 * 2 => Send transer message continuously (message with payload)
 */
gb_loopback_attr(type, d);
/* Size of transfer message payload: 0-4096 bytes */
gb_loopback_attr(size, u);
/* Time to wait between two messages: 0-1024 ms */
gb_loopback_attr(ms_wait, d);

#define dev_stats_attrs(name)						\
	&dev_attr_##name##_min.attr,					\
	&dev_attr_##name##_max.attr,					\
	&dev_attr_##name##_avg.attr

static struct attribute *loopback_attrs[] = {
	dev_stats_attrs(latency),
	dev_stats_attrs(frequency),
	dev_stats_attrs(throughput),
	&dev_attr_type.attr,
	&dev_attr_size.attr,
	&dev_attr_ms_wait.attr,
	&dev_attr_error.attr,
	NULL,
};
ATTRIBUTE_GROUPS(loopback);

struct gb_loopback_transfer_request {
	__le32	len;
	__u8	data[0];
};

struct gb_loopback_transfer_response {
	__u8	data[0];
};


static int gb_loopback_transfer(struct gb_loopback *gb,
				struct timeval *tping, u32 len)
{
	struct timeval ts, te;
	u64 elapsed_nsecs;
	struct gb_loopback_transfer_request *request;
	struct gb_loopback_transfer_response *response;
	int retval;

	request = kmalloc(len + sizeof(*request), GFP_KERNEL);
	if (!request)
		return -ENOMEM;
	response = kmalloc(len + sizeof(*response), GFP_KERNEL);
	if (!response) {
		kfree(request);
		return -ENOMEM;
	}

	request->len = cpu_to_le32(len);

	do_gettimeofday(&ts);
	retval = gb_operation_sync(gb->connection, GB_LOOPBACK_TYPE_TRANSFER,
				   request, len + sizeof(*request),
				   response, len + sizeof(*response));
	do_gettimeofday(&te);
	elapsed_nsecs = timeval_to_ns(&te) - timeval_to_ns(&ts);
	*tping = ns_to_timeval(elapsed_nsecs);

	if (retval)
		goto gb_error;

	if (memcmp(request->data, response->data, len))
		retval = -EREMOTEIO;

gb_error:
	kfree(request);
	kfree(response);

	return retval;
}

static int gb_loopback_ping(struct gb_loopback *gb, struct timeval *tping)
{
	struct timeval ts, te;
	u64 elapsed_nsecs;
	int retval;

	do_gettimeofday(&ts);
	retval = gb_operation_sync(gb->connection, GB_LOOPBACK_TYPE_PING,
				   NULL, 0, NULL, 0);
	do_gettimeofday(&te);
	elapsed_nsecs = timeval_to_ns(&te) - timeval_to_ns(&ts);
	*tping = ns_to_timeval(elapsed_nsecs);

	return retval;
}

static void gb_loopback_reset_stats(struct gb_loopback *gb)
{
	struct gb_loopback_stats reset = {
		.min = 0xffffffff,
	};
	memcpy(&gb->latency, &reset, sizeof(struct gb_loopback_stats));
	memcpy(&gb->throughput, &reset, sizeof(struct gb_loopback_stats));
	memcpy(&gb->frequency, &reset, sizeof(struct gb_loopback_stats));
	memset(&gb->ts, 0, sizeof(struct timeval));
}

static void gb_loopback_update_stats(struct gb_loopback_stats *stats,
					u64 elapsed_nsecs)
{
	u32 avg;

	if (elapsed_nsecs >= NSEC_PER_SEC) {
		if (!stats->count)
			avg = stats->sum * (elapsed_nsecs / NSEC_PER_SEC);
		else
			avg = stats->sum / stats->count;
		if (stats->min > avg)
			stats->min = avg;
		if (stats->max < avg)
			stats->max = avg;
		stats->avg = avg;
		stats->count = 0;
		stats->sum = 0;
	}
}

static void gb_loopback_freq_update(struct gb_loopback *gb)
{
	gb->frequency.sum++;
	gb_loopback_update_stats(&gb->frequency, gb->elapsed_nsecs);
}

static void gb_loopback_bw_update(struct gb_loopback *gb, int error)
{
	if (!error)
		gb->throughput.sum += gb->size * 2;
	gb_loopback_update_stats(&gb->throughput, gb->elapsed_nsecs);
}

static void gb_loopback_latency_update(struct gb_loopback *gb,
					struct timeval *tlat)
{
	u32 lat;
	u64 nsecs;

	nsecs = timeval_to_ns(tlat);
	lat = nsecs / NSEC_PER_MSEC;

	if (gb->latency.min > lat)
		gb->latency.min = lat;
	if (gb->latency.max < lat)
		gb->latency.max = lat;
	gb->latency.sum += lat;
	gb->latency.count++;
	gb_loopback_update_stats(&gb->latency, gb->elapsed_nsecs);
}

static int gb_loopback_fn(void *data)
{
	int error = 0;
	struct timeval tlat = {0, 0};
	struct gb_loopback *gb = (struct gb_loopback *)data;

	while (!kthread_should_stop()) {
		if (gb->type == 0) {
			msleep(1000);
			continue;
		}
		if (gb->type == 1)
			error = gb_loopback_ping(gb, &tlat);
		if (gb->type == 2)
			error = gb_loopback_transfer(gb, &tlat, gb->size);
		if (error)
			gb->error++;
		if (gb->ts.tv_usec == 0 && gb->ts.tv_sec == 0) {
			do_gettimeofday(&gb->ts);
			continue;
		}
		do_gettimeofday(&gb->te);
		gb->elapsed_nsecs = timeval_to_ns(&gb->te) -
					timeval_to_ns(&gb->ts);
		gb_loopback_freq_update(gb);
		if (gb->type == 2)
			gb_loopback_bw_update(gb, error);
		gb_loopback_latency_update(gb, &tlat);
		if (gb->elapsed_nsecs >= NSEC_PER_SEC)
			gb->ts = gb->te;
		if (gb->ms_wait)
			msleep(gb->ms_wait);

	}
	return 0;
}

static int gb_loopback_connection_init(struct gb_connection *connection)
{
	struct gb_loopback *gb;
	int retval;

	gb = kzalloc(sizeof(*gb), GFP_KERNEL);
	if (!gb)
		return -ENOMEM;

	gb->connection = connection;
	connection->private = gb;
	retval = sysfs_create_groups(&connection->dev.kobj, loopback_groups);
	if (retval)
		goto error;

	/* Check the version */
	retval = get_version(gb);
	if (retval)
		goto error;

	gb_loopback_reset_stats(gb);
	gb->task = kthread_run(gb_loopback_fn, gb, "gb_loopback");
	if (IS_ERR(gb->task)) {
		retval = IS_ERR(gb->task);
		goto error;
	}

	return 0;

error:
	kfree(gb);
	return retval;
}

static void gb_loopback_connection_exit(struct gb_connection *connection)
{
	struct gb_loopback *gb = connection->private;

	if (!IS_ERR_OR_NULL(gb->task))
		kthread_stop(gb->task);
	sysfs_remove_groups(&connection->dev.kobj, loopback_groups);
	kfree(gb);
}

static struct gb_protocol loopback_protocol = {
	.name			= "loopback",
	.id			= GREYBUS_PROTOCOL_LOOPBACK,
	.major			= GB_LOOPBACK_VERSION_MAJOR,
	.minor			= GB_LOOPBACK_VERSION_MINOR,
	.connection_init	= gb_loopback_connection_init,
	.connection_exit	= gb_loopback_connection_exit,
	.request_recv		= NULL,	/* no incoming requests */
};

gb_protocol_driver(&loopback_protocol);

MODULE_LICENSE("GPL v2");