/*
 * drivers/vservices/core_client.c
 *
 * Copyright (c) 2012-2018 General Dynamics
 * Copyright (c) 2014 Open Kernel Labs, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Client side core service application driver. This is responsible for:
 *
 *  - automatically connecting to the server when it becomes ready;
 *  - sending a reset command to the server if something has gone wrong; and
 *  - enumerating all the available services.
 *
 */

#include <linux/kernel.h>
#include <linux/completion.h>
#include <linux/workqueue.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/err.h>
#include <linux/module.h>

#include <vservices/types.h>
#include <vservices/transport.h>
#include <vservices/session.h>
#include <vservices/buffer.h>
#include <vservices/service.h>

#include <vservices/protocol/core/types.h>
#include <vservices/protocol/core/common.h>
#include <vservices/protocol/core/client.h>

#include "session.h"
#include "transport.h"
#include "compat.h"

struct core_client {
	struct vs_client_core_state	state;
	struct vs_service_device	*service;

	struct list_head		message_queue;
	struct mutex			message_queue_lock;
	struct work_struct		message_queue_work;
};

struct pending_reset {
	struct vs_service_device	*service;
	struct list_head		list;
};

#define to_core_client(x)	container_of(x, struct core_client, state)
#define dev_to_core_client(x)	to_core_client(dev_get_drvdata(x))

static int vs_client_core_fatal_error(struct vs_client_core_state *state)
{
	struct core_client *client = to_core_client(state);

	/* Force a transport level reset */
	dev_err(&client->service->dev," Fatal error - resetting session\n");
	return -EPROTO;
}

static struct core_client *
vs_client_session_core_client(struct vs_session_device *session)
{
	struct vs_service_device *core_service = session->core_service;

	if (!core_service)
		return NULL;

	return dev_to_core_client(&core_service->dev);
}

static ssize_t client_core_reset_service_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct vs_service_device *core_service = to_vs_service_device(dev);
	struct vs_session_device *session =
		vs_service_get_session(core_service);
	struct vs_service_device *target;
	vs_service_id_t service_id;
	unsigned long val;
	int err;

	/* Writing a valid service id to this file resets that service */
	err = kstrtoul(buf, 0, &val);
	if (err)
		return err;

	service_id = val;
	target = vs_session_get_service(session, service_id);
	if (!target)
		return -ENODEV;

	err = vs_service_reset(target, core_service);

	vs_put_service(target);
	return err < 0 ? err : count;
}

static DEVICE_ATTR(reset_service, S_IWUSR, NULL,
		client_core_reset_service_store);

static struct attribute *client_core_dev_attrs[] = {
	&dev_attr_reset_service.attr,
	NULL,
};

static const struct attribute_group client_core_attr_group = {
	.attrs = client_core_dev_attrs,
};

/*
 * Protocol callbacks
 */
static int
vs_client_core_handle_service_removed(struct vs_client_core_state *state,
		u32 service_id)
{
	struct core_client *client = to_core_client(state);
	struct vs_session_device *session =
			vs_service_get_session(client->service);
	struct vs_service_device *service;
	int ret;

	service = vs_session_get_service(session, service_id);
	if (!service)
		return -EINVAL;

	ret = vs_service_handle_delete(service);
	vs_put_service(service);
	return ret;
}

static int vs_client_core_create_service(struct core_client *client,
		struct vs_session_device *session, vs_service_id_t service_id,
		struct vs_string *protocol_name_string,
		struct vs_string *service_name_string)
{
	char *protocol_name, *service_name;
	struct vs_service_device *service;
	int ret = 0;

	protocol_name = vs_string_dup(protocol_name_string, GFP_KERNEL);
	if (!protocol_name) {
		ret = -ENOMEM;
		goto out;
	}

	service_name = vs_string_dup(service_name_string, GFP_KERNEL);
	if (!service_name) {
		ret = -ENOMEM;
		goto out_free_protocol_name;
	}

	service = vs_service_register(session, client->service, service_id,
			protocol_name, service_name, NULL);
	if (IS_ERR(service)) {
		ret = PTR_ERR(service);
		goto out_free_service_name;
	}

	vs_service_start(service);

out_free_service_name:
	kfree(service_name);
out_free_protocol_name:
	kfree(protocol_name);
out:
	return ret;
}

static int
vs_client_core_handle_service_created(struct vs_client_core_state *state,
		u32 service_id, struct vs_string service_name,
		struct vs_string protocol_name, struct vs_mbuf *mbuf)
{
	struct core_client *client = to_core_client(state);
	struct vs_session_device *session =
			vs_service_get_session(client->service);
	int err;

	vs_dev_debug(VS_DEBUG_CLIENT_CORE,
			vs_service_get_session(client->service),
			&client->service->dev, "Service info for %d received\n",
			service_id);

	err = vs_client_core_create_service(client, session, service_id,
			&protocol_name, &service_name);
	if (err)
		dev_err(&session->dev,
				"Failed to create service with id %d: %d\n",
				service_id, err);

	vs_client_core_core_free_service_created(state, &service_name,
			&protocol_name, mbuf);

	return err;
}

static int
vs_client_core_send_service_reset(struct core_client *client,
		struct vs_service_device *service)
{
	return vs_client_core_core_send_service_reset(&client->state,
			service->id, GFP_KERNEL);
}

static int
vs_client_core_queue_service_reset(struct vs_session_device *session,
		struct vs_service_device *service)
{
	struct core_client *client =
		vs_client_session_core_client(session);
	struct pending_reset *msg;

	if (!client)
		return -ENODEV;

	vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev,
			"Sending reset for service %d\n", service->id);

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

	mutex_lock(&client->message_queue_lock);

	/* put by message_queue_work */
	msg->service = vs_get_service(service);
	list_add_tail(&msg->list, &client->message_queue);

	mutex_unlock(&client->message_queue_lock);
	queue_work(client->service->work_queue, &client->message_queue_work);

	return 0;
}

static int vs_core_client_tx_ready(struct vs_client_core_state *state)
{
	struct core_client *client = to_core_client(state);

	queue_work(client->service->work_queue, &client->message_queue_work);

	return 0;
}

static void message_queue_work(struct work_struct *work)
{
	struct core_client *client = container_of(work, struct core_client,
			message_queue_work);
	struct vs_session_device *session =
		vs_service_get_session(client->service);
	struct pending_reset *msg;
	int err;

	vs_service_state_lock(client->service);
	if (!VSERVICE_CORE_STATE_IS_CONNECTED(client->state.state.core)) {
		vs_service_state_unlock(client->service);
		return;
	}

	vs_dev_debug(VS_DEBUG_CLIENT, session, &session->dev, "tx_ready\n");

	mutex_lock(&client->message_queue_lock);
	while (!list_empty(&client->message_queue)) {
		msg = list_first_entry(&client->message_queue,
				struct pending_reset, list);

		err = vs_client_core_send_service_reset(client, msg->service);

		/* If we're out of quota there's no point continuing */
		if (err == -ENOBUFS)
			break;

		/* Any other error is fatal */
		if (err < 0) {
			dev_err(&client->service->dev,
					"Failed to send pending reset for %d (%d) - resetting session\n",
					msg->service->id, err);
			vs_service_reset_nosync(client->service);
			break;
		}

		/*
		 * The message sent successfully - remove it from the queue.
		 * The corresponding vs_get_service() was done when the pending
		 * message was enqueued.
		 */
		vs_put_service(msg->service);
		list_del(&msg->list);
		kfree(msg);
	}
	mutex_unlock(&client->message_queue_lock);
	vs_service_state_unlock(client->service);
}

static int
vs_client_core_handle_server_ready(struct vs_client_core_state *state,
		u32 service_id, u32 in_quota, u32 out_quota, u32 in_bit_offset,
		u32 in_num_bits, u32 out_bit_offset, u32 out_num_bits)
{
	struct core_client *client = to_core_client(state);
	struct vs_session_device *session;
	struct vs_service_device *service;
	int ret;

	if (service_id == 0)
		return -EPROTO;

	if (!in_quota || !out_quota)
		return -EINVAL;

	session = vs_service_get_session(client->service);
	service = vs_session_get_service(session, service_id);
	if (!service)
		return -EINVAL;

	service->send_quota = in_quota;
	service->recv_quota = out_quota;
	service->notify_send_offset = in_bit_offset;
	service->notify_send_bits = in_num_bits;
	service->notify_recv_offset = out_bit_offset;
	service->notify_recv_bits = out_num_bits;

	ret = vs_service_enable(service);
	vs_put_service(service);
	return ret;
}

static int
vs_client_core_handle_service_reset(struct vs_client_core_state *state,
		u32 service_id)
{
	struct core_client *client = to_core_client(state);
	struct vs_session_device *session;

	if (service_id == 0)
		return -EPROTO;

	session = vs_service_get_session(client->service);

	return vs_service_handle_reset(session, service_id, true);
}

static void vs_core_client_start(struct vs_client_core_state *state)
{
	struct core_client *client = to_core_client(state);
	struct vs_session_device *session =
			vs_service_get_session(client->service);

	/* FIXME - start callback should return int */
	vs_dev_debug(VS_DEBUG_CLIENT_CORE, session, &client->service->dev,
			"Core client start\n");
}

static void vs_core_client_reset(struct vs_client_core_state *state)
{
	struct core_client *client = to_core_client(state);
	struct vs_session_device *session =
		vs_service_get_session(client->service);
	struct pending_reset *msg;

	/* Flush the pending resets - we're about to delete everything */
	while (!list_empty(&client->message_queue)) {
		msg = list_first_entry(&client->message_queue,
				struct pending_reset, list);
		vs_put_service(msg->service);
		list_del(&msg->list);
		kfree(msg);
	}

	vs_session_delete_noncore(session);

	/* Return to the initial quotas, until the next startup message */
	client->service->send_quota = 0;
	client->service->recv_quota = 1;
}

static int vs_core_client_startup(struct vs_client_core_state *state,
		u32 core_in_quota, u32 core_out_quota)
{
	struct core_client *client = to_core_client(state);
	struct vs_service_device *service = state->service;
	struct vs_session_device *session = vs_service_get_session(service);
	int ret;

	if (!core_in_quota || !core_out_quota)
		return -EINVAL;

	/*
	 * Update the service struct with our real quotas and tell the
	 * transport about the change
	 */

	service->send_quota = core_in_quota;
	service->recv_quota = core_out_quota;
	ret = session->transport->vt->service_start(session->transport, service);
	if (ret < 0)
		return ret;

	WARN_ON(!list_empty(&client->message_queue));

	return vs_client_core_core_req_connect(state, GFP_KERNEL);
}

static struct vs_client_core_state *
vs_core_client_alloc(struct vs_service_device *service)
{
	struct core_client *client;
	int err;

	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client)
		goto fail;

	client->service = service;
	INIT_LIST_HEAD(&client->message_queue);
	INIT_WORK(&client->message_queue_work, message_queue_work);
	mutex_init(&client->message_queue_lock);

	err = sysfs_create_group(&service->dev.kobj, &client_core_attr_group);
	if (err)
		goto fail_free_client;

	/*
	 * Default transport resources for the core service client. The
	 * server will inform us of the real quotas in the startup message.
	 * Note that it is important that the quotas never decrease, so these
	 * numbers are as small as possible.
	 */
	service->send_quota = 0;
	service->recv_quota = 1;
	service->notify_send_bits = 0;
	service->notify_send_offset = 0;
	service->notify_recv_bits = 0;
	service->notify_recv_offset = 0;

	return &client->state;

fail_free_client:
	kfree(client);
fail:
	return NULL;
}

static void vs_core_client_release(struct vs_client_core_state *state)
{
	struct core_client *client = to_core_client(state);

	sysfs_remove_group(&client->service->dev.kobj, &client_core_attr_group);
	kfree(client);
}

static struct vs_client_core vs_core_client_driver = {
	.alloc		= vs_core_client_alloc,
	.release	= vs_core_client_release,
	.start		= vs_core_client_start,
	.reset		= vs_core_client_reset,
	.tx_ready	= vs_core_client_tx_ready,

	.core = {
		.nack_connect		= vs_client_core_fatal_error,

		/* FIXME: Jira ticket SDK-3074 - ryanm. */
		.ack_disconnect		= vs_client_core_fatal_error,
		.nack_disconnect	= vs_client_core_fatal_error,

		.msg_service_created	= vs_client_core_handle_service_created,
		.msg_service_removed	= vs_client_core_handle_service_removed,

		.msg_startup		= vs_core_client_startup,
		/* FIXME: Jira ticket SDK-3074 - philipd. */
		.msg_shutdown		= vs_client_core_fatal_error,
		.msg_server_ready	= vs_client_core_handle_server_ready,
		.msg_service_reset	= vs_client_core_handle_service_reset,
	},
};

/*
 * Client bus driver
 */
static int vs_client_bus_match(struct device *dev, struct device_driver *driver)
{
	struct vs_service_device *service = to_vs_service_device(dev);
	struct vs_service_driver *vsdrv = to_vs_service_driver(driver);

	/* Don't match anything to the devio driver; it's bound manually */
	if (!vsdrv->protocol)
		return 0;

	WARN_ON_ONCE(service->is_server || vsdrv->is_server);

	/* Match if the protocol strings are the same */
	if (strcmp(service->protocol, vsdrv->protocol) == 0)
		return 1;

	return 0;
}

static ssize_t is_server_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	struct vs_service_device *service = to_vs_service_device(dev);

	return scnprintf(buf, PAGE_SIZE, "%d\n", service->is_server);
}

static DEVICE_ATTR_RO(is_server);

static ssize_t id_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	struct vs_service_device *service = to_vs_service_device(dev);

	return scnprintf(buf, PAGE_SIZE, "%d\n", service->id);
}

static DEVICE_ATTR_RO(id);

static ssize_t dev_protocol_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct vs_service_device *service = to_vs_service_device(dev);

	return scnprintf(buf, PAGE_SIZE, "%s\n", service->protocol ?: "");
}

static DEVICE_ATTR(protocol, 0444, dev_protocol_show, NULL);

static ssize_t service_name_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct vs_service_device *service = to_vs_service_device(dev);

	return scnprintf(buf, PAGE_SIZE, "%s\n", service->name);
}

static DEVICE_ATTR_RO(service_name);

static ssize_t quota_in_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct vs_service_device *service = to_vs_service_device(dev);

	return scnprintf(buf, PAGE_SIZE, "%d\n", service->send_quota);
}

static DEVICE_ATTR_RO(quota_in);

static ssize_t quota_out_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct vs_service_device *service = to_vs_service_device(dev);

	return scnprintf(buf, PAGE_SIZE, "%d\n", service->recv_quota);
}

static DEVICE_ATTR_RO(quota_out);

static struct attribute *vs_client_dev_attrs[] = {
	&dev_attr_id.attr,
	&dev_attr_is_server.attr,
	&dev_attr_protocol.attr,
	&dev_attr_service_name.attr,
	&dev_attr_quota_in.attr,
	&dev_attr_quota_out.attr,
	NULL,
};
ATTRIBUTE_GROUPS(vs_client_dev);

static ssize_t protocol_show(struct device_driver *drv, char *buf)
{
	struct vs_service_driver *driver = to_vs_service_driver(drv);

	return scnprintf(buf, PAGE_SIZE, "%s\n", driver->protocol);
}

static DRIVER_ATTR_RO(protocol);

static struct attribute *vs_client_drv_attrs[] = {
	&driver_attr_protocol.attr,
	NULL,
};
ATTRIBUTE_GROUPS(vs_client_drv);

struct bus_type vs_client_bus_type = {
	.name		= "vservices-client",
	.dev_groups	= vs_client_dev_groups,
	.drv_groups	= vs_client_drv_groups,
	.match		= vs_client_bus_match,
	.probe		= vs_service_bus_probe,
	.remove		= vs_service_bus_remove,
	.uevent		= vs_service_bus_uevent,
};
EXPORT_SYMBOL(vs_client_bus_type);

/*
 * Client session driver
 */
static int vs_client_session_probe(struct device *dev)
{
	struct vs_session_device *session = to_vs_session_device(dev);
	struct vs_service_device *service;
	char *protocol, *name;
	int ret = 0;

	if (session->is_server) {
		ret = -ENODEV;
		goto fail;
	}

	/* create a service for the core protocol client */
	protocol = kstrdup(VSERVICE_CORE_PROTOCOL_NAME, GFP_KERNEL);
	if (!protocol) {
		ret = -ENOMEM;
		goto fail;
	}

	name = kstrdup("core", GFP_KERNEL);
	if (!name) {
		ret = -ENOMEM;
		goto fail_free_protocol;
	}

	service = vs_service_register(session, NULL, 0, protocol, name, NULL);
	if (IS_ERR(service)) {
		ret = PTR_ERR(service);
		goto fail_free_name;
	}

fail_free_name:
	kfree(name);
fail_free_protocol:
	kfree(protocol);
fail:
	return ret;
}

static int
vs_client_session_send_service_reset(struct vs_session_device *session,
		struct vs_service_device *service)
{
	if (WARN_ON(service->id == 0))
		return -EINVAL;

	return vs_client_core_queue_service_reset(session, service);
}

static struct vs_session_driver vs_client_session_driver = {
	.driver	= {
		.name			= "vservices-client-session",
		.owner			= THIS_MODULE,
		.bus			= &vs_session_bus_type,
		.probe			= vs_client_session_probe,
		.suppress_bind_attrs	= true,
	},
	.is_server		= false,
	.service_bus		= &vs_client_bus_type,
	.service_local_reset	= vs_client_session_send_service_reset,
};

static int __init vs_core_client_init(void)
{
	int ret;

	ret = bus_register(&vs_client_bus_type);
	if (ret)
		goto fail_bus_register;

#ifdef CONFIG_VSERVICES_CHAR_DEV
	vs_devio_client_driver.driver.bus = &vs_client_bus_type;
	vs_devio_client_driver.driver.owner = THIS_MODULE;
	ret = driver_register(&vs_devio_client_driver.driver);
	if (ret)
		goto fail_devio_register;
#endif

	ret = driver_register(&vs_client_session_driver.driver);
	if (ret)
		goto fail_driver_register;

	ret = vservice_core_client_register(&vs_core_client_driver,
			"vs_core_client");
	if (ret)
		goto fail_core_register;

	vservices_client_root = kobject_create_and_add("client-sessions",
			vservices_root);
	if (!vservices_client_root) {
		ret = -ENOMEM;
		goto fail_create_root;
	}

	return 0;

fail_create_root:
	vservice_core_client_unregister(&vs_core_client_driver);
fail_core_register:
	driver_unregister(&vs_client_session_driver.driver);
fail_driver_register:
#ifdef CONFIG_VSERVICES_CHAR_DEV
	driver_unregister(&vs_devio_client_driver.driver);
	vs_devio_client_driver.driver.bus = NULL;
	vs_devio_client_driver.driver.owner = NULL;
fail_devio_register:
#endif
	bus_unregister(&vs_client_bus_type);
fail_bus_register:
	return ret;
}

static void __exit vs_core_client_exit(void)
{
	kobject_put(vservices_client_root);
	vservice_core_client_unregister(&vs_core_client_driver);
	driver_unregister(&vs_client_session_driver.driver);
#ifdef CONFIG_VSERVICES_CHAR_DEV
	driver_unregister(&vs_devio_client_driver.driver);
	vs_devio_client_driver.driver.bus = NULL;
	vs_devio_client_driver.driver.owner = NULL;
#endif
	bus_unregister(&vs_client_bus_type);
}

subsys_initcall(vs_core_client_init);
module_exit(vs_core_client_exit);

MODULE_DESCRIPTION("OKL4 Virtual Services Core Client Driver");
MODULE_AUTHOR("Open Kernel Labs, Inc");
