diff --git a/drivers/Kconfig b/drivers/Kconfig index da7406c781e2..7e8029cb7e2d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -137,6 +137,8 @@ source "drivers/hv/Kconfig" source "drivers/xen/Kconfig" +source "drivers/vservices/Kconfig" + source "drivers/staging/Kconfig" source "drivers/platform/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 7baf29769c4d..abf600a7daf7 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -11,6 +11,8 @@ obj-y += bus/ obj-$(CONFIG_GENERIC_PHY) += phy/ +obj-$(CONFIG_VSERVICES_SUPPORT) += vservices/ + # GPIO must come after pinctrl as gpios may need to mux pins etc obj-$(CONFIG_PINCTRL) += pinctrl/ obj-$(CONFIG_GPIOLIB) += gpio/ diff --git a/drivers/vservices/Kconfig b/drivers/vservices/Kconfig new file mode 100644 index 000000000000..16b3bda86a9f --- /dev/null +++ b/drivers/vservices/Kconfig @@ -0,0 +1,81 @@ +# +# OKL4 Virtual Services framework +# + +menuconfig VSERVICES_SUPPORT + tristate "OKL4 Virtual Services support" + default OKL4_GUEST || OKL4_VIRTUALISATION + select HOTPLUG + help + This option adds core support for OKL4 Virtual Services. The Virtual + Services framework is an inter-OS device/service sharing + protocol which is supported on OKL4 Microvisor virtualization + platforms. You will also need drivers from the following menu in + order to make use of it. + +if VSERVICES_SUPPORT + +config VSERVICES_CHAR_DEV + bool "Virtual Services user-space service API" + default y + help + Select this if you want to use user-space service drivers. You will + also need udev rules that create device nodes, and protocol code + generated by the OK Mill tool. + +config VSERVICES_DEBUG + bool "Virtual Services debugging support" + help + Select this if you want to enable Virtual Services core framework + debugging. The debug messages for various components of the Virtual + Services core framework can be toggled at runtime on a per-session + basis via sysfs. When Virtual Services debugging is enabled here, + but disabled at runtime it has a minimal performance impact. + +config VSERVICES_LOCK_DEBUG + bool "Debug Virtual Services state locks" + default DEBUG_KERNEL + help + This option enables some runtime checks that Virtual Services + state lock functions are used correctly in service drivers. + +config VSERVICES_SERVER + tristate "Virtual Services server support" + depends on SYSFS + default y + help + This option adds support for Virtual Services servers, which allows + exporting of services from this Linux to other environments. Servers + are created at runtime by writing to files in + /sys/bus/vservices-server. + +config VSERVICES_CLIENT + tristate "Virtual Services client support" + default y + help + This option adds support for Virtual Services clients, which allows + connecting to services exported from other environments. + +config VSERVICES_SKELETON_DRIVER + tristate "Virtual Services skeleton driver" + depends on VSERVICES_SERVER || VSERVICES_CLIENT + default n + help + This option adds support for a skeleton virtual service driver. This + driver can be used for templating or testing of virtual service + drivers. If unsure say N. + +config VSERVICES_NAMED_DEVICE + bool "Virtual Services use named device node in /dev" + default n + help + Select this if you want to use a named device name over a numeric + device name in /dev + +source "drivers/vservices/transport/Kconfig" + +source "drivers/vservices/protocol/Kconfig" + +source "drivers/vservices/Kconfig.stacks" + +endif # VSERVICES_SUPPORT diff --git a/drivers/vservices/Kconfig.stacks b/drivers/vservices/Kconfig.stacks new file mode 100644 index 000000000000..97eba53df5dd --- /dev/null +++ b/drivers/vservices/Kconfig.stacks @@ -0,0 +1,7 @@ +# +# vServices drivers configuration +# + +menu "Client and Server drivers" + +endmenu diff --git a/drivers/vservices/Makefile b/drivers/vservices/Makefile new file mode 100644 index 000000000000..4ce9e48c72da --- /dev/null +++ b/drivers/vservices/Makefile @@ -0,0 +1,14 @@ +ccflags-y += -Werror +ccflags-$(CONFIG_VSERVICES_DEBUG) += -DDEBUG + +obj-$(CONFIG_VSERVICES_SUPPORT) += vservices.o +vservices-objs-$(CONFIG_VSERVICES_CHAR_DEV) += devio.o +vservices-objs = session.o $(vservices-objs-y) + +obj-$(CONFIG_VSERVICES_CLIENT) += core_client.o +obj-$(CONFIG_VSERVICES_SERVER) += core_server.o + +obj-$(CONFIG_VSERVICES_SKELETON_DRIVER) += vservices_skeleton_driver.o +vservices_skeleton_driver-objs = skeleton_driver.o + +obj-$(CONFIG_VSERVICES_SUPPORT) += protocol/ diff --git a/drivers/vservices/compat.h b/drivers/vservices/compat.h new file mode 100644 index 000000000000..5f6926dc9f78 --- /dev/null +++ b/drivers/vservices/compat.h @@ -0,0 +1,59 @@ +/* + * drivers/vservices/compat.h + * + * 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. + * + * Wrapper functions/definitions for compatibility between differnet kernel + * versions. + */ + +#ifndef _VSERVICES_COMPAT_H +#define _VSERVICES_COMPAT_H + +#include +#include + +/* The INIT_WORK_ONSTACK macro has a slightly different name in older kernels */ +#ifndef INIT_WORK_ONSTACK +#define INIT_WORK_ONSTACK(_work, _func) INIT_WORK_ON_STACK(_work, _func) +#endif + +/* + * We require a workqueue with no concurrency. This is provided by + * create_singlethread_workqueue() in kernel prior to 2.6.36. + * In later versions, create_singlethread_workqueue() enables WQ_MEM_RECLAIM and + * thus WQ_RESCUER, which allows work items to be grabbed by a rescuer thread + * and run concurrently if the queue is running too slowly. We must use + * alloc_ordered_workqueue() instead, to disable the rescuer. + */ +static inline struct workqueue_struct * +vs_create_workqueue(const char *name) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) + return create_singlethread_workqueue(name); +#else + return alloc_ordered_workqueue(name, 0); +#endif +} + +/* + * The max3 macro has only been present from 2.6.37 + * (commit: f27c85c56b32c42bcc54a43189c1e00fdceb23ec) + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) +#define max3(x, y, z) ({ \ + typeof(x) _max1 = (x); \ + typeof(y) _max2 = (y); \ + typeof(z) _max3 = (z); \ + (void) (&_max1 == &_max2); \ + (void) (&_max1 == &_max3); \ + _max1 > _max2 ? (_max1 > _max3 ? _max1 : _max3) : \ + (_max2 > _max3 ? _max2 : _max3); }) +#endif + +#endif /* _VSERVICES_COMPAT_H */ diff --git a/drivers/vservices/core_client.c b/drivers/vservices/core_client.c new file mode 100644 index 000000000000..a48558cd601e --- /dev/null +++ b/drivers/vservices/core_client.c @@ -0,0 +1,733 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 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 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 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 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 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 struct device_attribute vs_client_dev_attrs[] = { + __ATTR_RO(id), + __ATTR_RO(is_server), + __ATTR(protocol, S_IRUGO, dev_protocol_show, NULL), + __ATTR_RO(service_name), + __ATTR_RO(quota_in), + __ATTR_RO(quota_out), + __ATTR_NULL +}; + +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); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) +static struct driver_attribute vs_client_drv_attrs[] = { + __ATTR_RO(protocol), + __ATTR_NULL +}; +#else +static DRIVER_ATTR_RO(protocol); + +static struct attribute *vs_client_drv_attrs[] = { + &driver_attr_protocol.attr, + NULL, +}; +ATTRIBUTE_GROUPS(vs_client_drv); +#endif + +struct bus_type vs_client_bus_type = { + .name = "vservices-client", + .dev_attrs = vs_client_dev_attrs, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) + .drv_attrs = vs_client_drv_attrs, +#else + .drv_groups = vs_client_drv_groups, +#endif + .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"); diff --git a/drivers/vservices/core_server.c b/drivers/vservices/core_server.c new file mode 100644 index 000000000000..44872a3bcb51 --- /dev/null +++ b/drivers/vservices/core_server.c @@ -0,0 +1,1649 @@ +/* + * drivers/vservices/core_server.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. + * + * Server side core service application driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "transport.h" +#include "session.h" +#include "compat.h" + +#define VSERVICE_CORE_SERVICE_NAME "core" + +struct core_server { + struct vs_server_core_state state; + struct vs_service_device *service; + + /* + * A list of messages to send, a mutex protecting it, and a + * work item to process the list. + */ + struct list_head message_queue; + struct mutex message_queue_lock; + struct work_struct message_queue_work; + + struct mutex alloc_lock; + + /* The following are all protected by alloc_lock. */ + unsigned long *in_notify_map; + int in_notify_map_bits; + + unsigned long *out_notify_map; + int out_notify_map_bits; + + unsigned in_quota_remaining; + unsigned out_quota_remaining; +}; + +/* + * Used for message deferral when the core service is over quota. + */ +struct pending_message { + vservice_core_message_id_t type; + struct vs_service_device *service; + struct list_head list; +}; + +#define to_core_server(x) container_of(x, struct core_server, state) +#define dev_to_core_server(x) to_core_server(dev_get_drvdata(x)) + +static struct vs_session_device * +vs_core_server_session(struct core_server *server) +{ + return vs_service_get_session(server->service); +} + +static struct core_server * +vs_server_session_core_server(struct vs_session_device *session) +{ + struct vs_service_device *core_service = session->core_service; + + if (!core_service) + return NULL; + + return dev_to_core_server(&core_service->dev); +} + +static int vs_server_core_send_service_removed(struct core_server *server, + struct vs_service_device *service) +{ + return vs_server_core_core_send_service_removed(&server->state, + service->id, GFP_KERNEL); +} + +static bool +cancel_pending_created(struct core_server *server, + struct vs_service_device *service) +{ + struct pending_message *msg; + + list_for_each_entry(msg, &server->message_queue, list) { + if (msg->type == VSERVICE_CORE_CORE_MSG_SERVICE_CREATED && + msg->service == service) { + vs_put_service(msg->service); + list_del(&msg->list); + kfree(msg); + + /* there can only be one */ + return true; + } + } + + return false; +} + +static int vs_server_core_queue_service_removed(struct core_server *server, + struct vs_service_device *service) +{ + struct pending_message *msg; + + lockdep_assert_held(&service->ready_lock); + + mutex_lock(&server->message_queue_lock); + + /* + * If we haven't sent the notification that the service was created, + * nuke it and do nothing else. + * + * This is not just an optimisation; see below. + */ + if (cancel_pending_created(server, service)) { + mutex_unlock(&server->message_queue_lock); + return 0; + } + + /* + * Do nothing if the core state is not connected. We must avoid + * queueing service_removed messages on a reset service. + * + * Note that we cannot take the core server state lock here, because + * we may (or may not) have been called from a core service message + * handler. Thus, we must beware of races with changes to this + * condition: + * + * - It becomes true when the req_connect handler sends an + * ack_connect, *after* it queues service_created for each existing + * service (while holding the service ready lock). The handler sends + * ack_connect with the message queue lock held. + * + * - If we see the service as connected, then the req_connect + * handler has already queued and sent a service_created for this + * service, so it's ok for us to send a service_removed. + * + * - If we see it as disconnected, the req_connect handler hasn't + * taken the message queue lock to send ack_connect yet, and thus + * has not released the service state lock; so if it queued a + * service_created we caught it in the flush above before it was + * sent. + * + * - It becomes false before the reset / disconnect handlers are + * called and those will both flush the message queue afterwards. + * + * - If we see the service as connected, then the reset / disconnect + * handler is going to flush the message. + * + * - If we see it disconnected, the state change has occurred and + * implicitly had the same effect as this message, so doing + * nothing is correct. + * + * Note that ordering in all of the above cases is guaranteed by the + * message queue lock. + */ + if (!VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) { + mutex_unlock(&server->message_queue_lock); + return 0; + } + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) { + mutex_unlock(&server->message_queue_lock); + return -ENOMEM; + } + + msg->type = VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED; + /* put by message_queue_work */ + msg->service = vs_get_service(service); + + list_add_tail(&msg->list, &server->message_queue); + + mutex_unlock(&server->message_queue_lock); + queue_work(server->service->work_queue, &server->message_queue_work); + + return 0; +} + +static int vs_server_core_send_service_created(struct core_server *server, + struct vs_service_device *service) +{ + struct vs_session_device *session = + vs_service_get_session(server->service); + + struct vs_mbuf *mbuf; + struct vs_string service_name, protocol_name; + size_t service_name_len, protocol_name_len; + + int err; + + mbuf = vs_server_core_core_alloc_service_created(&server->state, + &service_name, &protocol_name, GFP_KERNEL); + + if (IS_ERR(mbuf)) + return PTR_ERR(mbuf); + + vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev, + "Sending service created message for %d (%s:%s)\n", + service->id, service->name, service->protocol); + + service_name_len = strlen(service->name); + protocol_name_len = strlen(service->protocol); + + if (service_name_len > vs_string_max_size(&service_name) || + protocol_name_len > vs_string_max_size(&protocol_name)) { + dev_err(&session->dev, + "Invalid name/protocol for service %d (%s:%s)\n", + service->id, service->name, + service->protocol); + err = -EINVAL; + goto fail; + } + + vs_string_copyin(&service_name, service->name); + vs_string_copyin(&protocol_name, service->protocol); + + err = vs_server_core_core_send_service_created(&server->state, + service->id, service_name, protocol_name, mbuf); + if (err) { + dev_err(&session->dev, + "Fatal error sending service creation message for %d (%s:%s): %d\n", + service->id, service->name, + service->protocol, err); + goto fail; + } + + return 0; + +fail: + vs_server_core_core_free_service_created(&server->state, + &service_name, &protocol_name, mbuf); + + return err; +} + +static int vs_server_core_queue_service_created(struct core_server *server, + struct vs_service_device *service) +{ + struct pending_message *msg; + + lockdep_assert_held(&service->ready_lock); + lockdep_assert_held(&server->service->state_mutex); + + mutex_lock(&server->message_queue_lock); + + /* Do nothing if the core state is disconnected. */ + if (!VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) { + mutex_unlock(&server->message_queue_lock); + return 0; + } + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) { + mutex_unlock(&server->message_queue_lock); + return -ENOMEM; + } + + msg->type = VSERVICE_CORE_CORE_MSG_SERVICE_CREATED; + /* put by message_queue_work */ + msg->service = vs_get_service(service); + + list_add_tail(&msg->list, &server->message_queue); + + mutex_unlock(&server->message_queue_lock); + queue_work(server->service->work_queue, &server->message_queue_work); + + return 0; +} + +static struct vs_service_device * +__vs_server_core_register_service(struct vs_session_device *session, + vs_service_id_t service_id, struct vs_service_device *owner, + const char *name, const char *protocol, const void *plat_data) +{ + if (!session->is_server) + return ERR_PTR(-ENODEV); + + if (!name || strnlen(name, VSERVICE_CORE_SERVICE_NAME_SIZE + 1) > + VSERVICE_CORE_SERVICE_NAME_SIZE || name[0] == '\n') + return ERR_PTR(-EINVAL); + + /* The server core must only be registered as service_id zero */ + if (service_id == 0 && (owner != NULL || + strcmp(name, VSERVICE_CORE_SERVICE_NAME) != 0 || + strcmp(protocol, VSERVICE_CORE_PROTOCOL_NAME) != 0)) + return ERR_PTR(-EINVAL); + + return vs_service_register(session, owner, service_id, protocol, name, + plat_data); +} + +static struct vs_service_device * +vs_server_core_create_service(struct core_server *server, + struct vs_session_device *session, + struct vs_service_device *owner, vs_service_id_t service_id, + const char *name, const char *protocol, const void *plat_data) +{ + struct vs_service_device *service; + + service = __vs_server_core_register_service(session, service_id, + owner, name, protocol, plat_data); + if (IS_ERR(service)) + return service; + + if (protocol) { + vs_service_state_lock(server->service); + vs_service_start(service); + if (VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) + vs_service_enable(service); + vs_service_state_unlock(server->service); + } + + return service; +} + +static int +vs_server_core_send_service_reset_ready(struct core_server *server, + vservice_core_message_id_t type, + struct vs_service_device *service) +{ + bool is_reset = (type == VSERVICE_CORE_CORE_MSG_SERVICE_RESET); + struct vs_session_device *session __maybe_unused = + vs_service_get_session(server->service); + int err; + + vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev, + "Sending %s for service %d\n", + is_reset ? "reset" : "ready", service->id); + + if (is_reset) + err = vs_server_core_core_send_service_reset(&server->state, + service->id, GFP_KERNEL); + else + err = vs_server_core_core_send_server_ready(&server->state, + service->id, service->recv_quota, + service->send_quota, + service->notify_recv_offset, + service->notify_recv_bits, + service->notify_send_offset, + service->notify_send_bits, + GFP_KERNEL); + + return err; +} + +static bool +cancel_pending_ready(struct core_server *server, + struct vs_service_device *service) +{ + struct pending_message *msg; + + list_for_each_entry(msg, &server->message_queue, list) { + if (msg->type == VSERVICE_CORE_CORE_MSG_SERVER_READY && + msg->service == service) { + vs_put_service(msg->service); + list_del(&msg->list); + kfree(msg); + + /* there can only be one */ + return true; + } + } + + return false; +} + +static int +vs_server_core_queue_service_reset_ready(struct core_server *server, + vservice_core_message_id_t type, + struct vs_service_device *service) +{ + bool is_reset = (type == VSERVICE_CORE_CORE_MSG_SERVICE_RESET); + struct pending_message *msg; + + mutex_lock(&server->message_queue_lock); + + /* + * If this is a reset, and there is an outgoing ready in the + * queue, we must cancel it so it can't be sent with invalid + * transport resources, and then return immediately so we + * don't send a redundant reset. + */ + if (is_reset && cancel_pending_ready(server, service)) { + mutex_unlock(&server->message_queue_lock); + return VS_SERVICE_ALREADY_RESET; + } + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) { + mutex_unlock(&server->message_queue_lock); + return -ENOMEM; + } + + msg->type = type; + /* put by message_queue_work */ + msg->service = vs_get_service(service); + list_add_tail(&msg->list, &server->message_queue); + + mutex_unlock(&server->message_queue_lock); + queue_work(server->service->work_queue, &server->message_queue_work); + + return 0; +} + +static int vs_core_server_tx_ready(struct vs_server_core_state *state) +{ + struct core_server *server = to_core_server(state); + struct vs_session_device *session __maybe_unused = + vs_service_get_session(server->service); + + vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev, "tx_ready\n"); + + queue_work(server->service->work_queue, &server->message_queue_work); + + return 0; +} + +static void message_queue_work(struct work_struct *work) +{ + struct core_server *server = container_of(work, struct core_server, + message_queue_work); + struct pending_message *msg; + int err; + + vs_service_state_lock(server->service); + + if (!VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) { + vs_service_state_unlock(server->service); + return; + } + + /* + * If any pending message fails we exit the loop immediately so that + * we preserve the message order. + */ + mutex_lock(&server->message_queue_lock); + while (!list_empty(&server->message_queue)) { + msg = list_first_entry(&server->message_queue, + struct pending_message, list); + + switch (msg->type) { + case VSERVICE_CORE_CORE_MSG_SERVICE_CREATED: + err = vs_server_core_send_service_created(server, + msg->service); + break; + + case VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED: + err = vs_server_core_send_service_removed(server, + msg->service); + break; + + case VSERVICE_CORE_CORE_MSG_SERVICE_RESET: + case VSERVICE_CORE_CORE_MSG_SERVER_READY: + err = vs_server_core_send_service_reset_ready( + server, msg->type, msg->service); + break; + + default: + dev_warn(&server->service->dev, + "Don't know how to handle pending message type %d\n", + msg->type); + err = 0; + break; + } + + /* + * If we're out of quota we exit and wait for tx_ready to + * queue us again. + */ + if (err == -ENOBUFS) + break; + + /* Any other error is fatal */ + if (err < 0) { + dev_err(&server->service->dev, + "Failed to send pending message type %d: %d - resetting session\n", + msg->type, err); + vs_service_reset_nosync(server->service); + break; + } + + /* + * The message sent successfully - remove it from the + * queue. The corresponding vs_get_service() was done + * when the pending message was created. + */ + vs_put_service(msg->service); + list_del(&msg->list); + kfree(msg); + } + mutex_unlock(&server->message_queue_lock); + + vs_service_state_unlock(server->service); + + return; +} + +/* + * Core server sysfs interface + */ +static ssize_t server_core_create_service_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_session_device *session = to_vs_session_device(dev->parent); + struct core_server *server = dev_to_core_server(&service->dev); + struct vs_service_device *new_service; + char *p; + ssize_t ret = count; + + /* FIXME - Buffer sizes are not defined in generated headers */ + /* discard leading whitespace */ + while (count && isspace(*buf)) { + buf++; + count--; + } + if (!count) { + dev_info(dev, "empty service name\n"); + return -EINVAL; + } + /* discard trailing whitespace */ + while (count && isspace(buf[count - 1])) + count--; + + if (count > VSERVICE_CORE_SERVICE_NAME_SIZE) { + dev_info(dev, "service name too long (max %d)\n", VSERVICE_CORE_SERVICE_NAME_SIZE); + return -EINVAL; + } + + p = kstrndup(buf, count, GFP_KERNEL); + + /* + * Writing a service name to this file creates a new service. The + * service is created without a protocol. It will appear in sysfs + * but will not be bound to a driver until a valid protocol name + * has been written to the created devices protocol sysfs attribute. + */ + new_service = vs_server_core_create_service(server, session, service, + VS_SERVICE_AUTO_ALLOCATE_ID, p, NULL, NULL); + if (IS_ERR(new_service)) + ret = PTR_ERR(new_service); + + kfree(p); + + return ret; +} + +static ssize_t server_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 does a reset of 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 -EINVAL; + + err = vs_service_reset(target, core_service); + + vs_put_service(target); + return err < 0 ? err : count; +} + +static ssize_t server_core_remove_service_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_session_device *session = vs_service_get_session(service); + struct vs_service_device *target; + vs_service_id_t service_id; + unsigned long val; + int err; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + service_id = val; + if (service_id == 0) { + /* + * We don't allow removing the core service this way. The + * core service will be removed when the session is removed. + */ + return -EINVAL; + } + + target = vs_session_get_service(session, service_id); + if (!target) + return -EINVAL; + + err = vs_service_delete(target, service); + + vs_put_service(target); + return err < 0 ? err : count; +} + +static DEVICE_ATTR(create_service, S_IWUSR, + NULL, server_core_create_service_store); +static DEVICE_ATTR(reset_service, S_IWUSR, + NULL, server_core_reset_service_store); +static DEVICE_ATTR(remove_service, S_IWUSR, + NULL, server_core_remove_service_store); + +static struct attribute *server_core_dev_attrs[] = { + &dev_attr_create_service.attr, + &dev_attr_reset_service.attr, + &dev_attr_remove_service.attr, + NULL, +}; + +static const struct attribute_group server_core_attr_group = { + .attrs = server_core_dev_attrs, +}; + +static int init_transport_resource_allocation(struct core_server *server) +{ + struct vs_session_device *session = vs_core_server_session(server); + struct vs_transport *transport = session->transport; + size_t size; + int err; + + mutex_init(&server->alloc_lock); + mutex_lock(&server->alloc_lock); + + transport->vt->get_quota_limits(transport, &server->out_quota_remaining, + &server->in_quota_remaining); + + transport->vt->get_notify_bits(transport, &server->out_notify_map_bits, + &server->in_notify_map_bits); + + size = BITS_TO_LONGS(server->in_notify_map_bits) * + sizeof(unsigned long); + server->in_notify_map = kzalloc(size, GFP_KERNEL); + if (server->in_notify_map_bits && !server->in_notify_map) { + err = -ENOMEM; + goto fail; + } + + size = BITS_TO_LONGS(server->out_notify_map_bits) * + sizeof(unsigned long); + server->out_notify_map = kzalloc(size, GFP_KERNEL); + if (server->out_notify_map_bits && !server->out_notify_map) { + err = -ENOMEM; + goto fail_free_in_bits; + } + + mutex_unlock(&server->alloc_lock); + + return 0; + +fail_free_in_bits: + kfree(server->in_notify_map); +fail: + mutex_unlock(&server->alloc_lock); + return err; +} + +static int alloc_quota(unsigned minimum, unsigned best, unsigned set, + unsigned *remaining) +{ + unsigned quota; + + if (set) { + quota = set; + + if (quota > *remaining) + return -ENOSPC; + } else if (best) { + quota = min(best, *remaining); + } else { + quota = minimum; + } + + if (quota < minimum) + return -ENOSPC; + + *remaining -= quota; + + return min_t(unsigned, quota, INT_MAX); +} + +static int alloc_notify_bits(unsigned notify_count, unsigned long *map, + unsigned nr_bits) +{ + unsigned offset; + + if (notify_count) { + offset = bitmap_find_next_zero_area(map, nr_bits, 0, + notify_count, 0); + + if (offset >= nr_bits || offset > (unsigned)INT_MAX) + return -ENOSPC; + + bitmap_set(map, offset, notify_count); + } else { + offset = 0; + } + + return offset; +} + +/* + * alloc_transport_resources - Allocates the quotas and notification bits for + * a service. + * @server: the core service state. + * @service: the service device to allocate resources for. + * + * This function allocates message quotas and notification bits. It is called + * for the core service in alloc(), and for every other service by the server + * bus probe() function. + */ +static int alloc_transport_resources(struct core_server *server, + struct vs_service_device *service) +{ + struct vs_session_device *session __maybe_unused = + vs_service_get_session(service); + unsigned in_bit_offset, out_bit_offset; + unsigned in_quota, out_quota; + int ret; + struct vs_service_driver *driver; + + if (WARN_ON(!service->dev.driver)) + return -ENODEV; + + mutex_lock(&server->alloc_lock); + + driver = to_vs_service_driver(service->dev.driver); + + /* Quota allocations */ + ret = alloc_quota(driver->in_quota_min, driver->in_quota_best, + service->in_quota_set, &server->in_quota_remaining); + if (ret < 0) { + dev_err(&service->dev, "cannot allocate in quota\n"); + goto fail_in_quota; + } + in_quota = ret; + + ret = alloc_quota(driver->out_quota_min, driver->out_quota_best, + service->out_quota_set, &server->out_quota_remaining); + if (ret < 0) { + dev_err(&service->dev, "cannot allocate out quota\n"); + goto fail_out_quota; + } + out_quota = ret; + + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev, + "%d: quota in: %u out: %u; remaining in: %u out: %u\n", + service->id, in_quota, out_quota, + server->in_quota_remaining, + server->out_quota_remaining); + + /* Notification bit allocations */ + ret = alloc_notify_bits(service->notify_recv_bits, + server->in_notify_map, server->in_notify_map_bits); + if (ret < 0) { + dev_err(&service->dev, "cannot allocate in notify bits\n"); + goto fail_in_notify; + } + in_bit_offset = ret; + + ret = alloc_notify_bits(service->notify_send_bits, + server->out_notify_map, server->out_notify_map_bits); + if (ret < 0) { + dev_err(&service->dev, "cannot allocate out notify bits\n"); + goto fail_out_notify; + } + out_bit_offset = ret; + + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev, + "notify bits in: %u/%u out: %u/%u\n", + in_bit_offset, service->notify_recv_bits, + out_bit_offset, service->notify_send_bits); + + /* Fill in the device's allocations */ + service->recv_quota = in_quota; + service->send_quota = out_quota; + service->notify_recv_offset = in_bit_offset; + service->notify_send_offset = out_bit_offset; + + mutex_unlock(&server->alloc_lock); + + return 0; + +fail_out_notify: + if (service->notify_recv_bits) + bitmap_clear(server->in_notify_map, + in_bit_offset, service->notify_recv_bits); +fail_in_notify: + server->out_quota_remaining += out_quota; +fail_out_quota: + server->in_quota_remaining += in_quota; +fail_in_quota: + + mutex_unlock(&server->alloc_lock); + + service->recv_quota = 0; + service->send_quota = 0; + service->notify_recv_bits = 0; + service->notify_recv_offset = 0; + service->notify_send_bits = 0; + service->notify_send_offset = 0; + + return ret; +} + +/* + * free_transport_resources - Frees the quotas and notification bits for + * a non-core service. + * @server: the core service state. + * @service: the service device to free resources for. + * + * This function is called by the server to free message quotas and + * notification bits that were allocated by alloc_transport_resources. It must + * only be called when the target service is in reset, and must be called with + * the core service's state lock held. + */ +static int free_transport_resources(struct core_server *server, + struct vs_service_device *service) +{ + mutex_lock(&server->alloc_lock); + + if (service->notify_recv_bits) + bitmap_clear(server->in_notify_map, + service->notify_recv_offset, + service->notify_recv_bits); + + if (service->notify_send_bits) + bitmap_clear(server->out_notify_map, + service->notify_send_offset, + service->notify_send_bits); + + server->in_quota_remaining += service->recv_quota; + server->out_quota_remaining += service->send_quota; + + mutex_unlock(&server->alloc_lock); + + service->recv_quota = 0; + service->send_quota = 0; + service->notify_recv_bits = 0; + service->notify_recv_offset = 0; + service->notify_send_bits = 0; + service->notify_send_offset = 0; + + return 0; +} + +static struct vs_server_core_state * +vs_core_server_alloc(struct vs_service_device *service) +{ + struct core_server *server; + int err; + + if (WARN_ON(service->id != 0)) + goto fail; + + server = kzalloc(sizeof(*server), GFP_KERNEL); + if (!server) + goto fail; + + server->service = service; + INIT_LIST_HEAD(&server->message_queue); + INIT_WORK(&server->message_queue_work, message_queue_work); + mutex_init(&server->message_queue_lock); + + err = init_transport_resource_allocation(server); + if (err) + goto fail_init_alloc; + + err = alloc_transport_resources(server, service); + if (err) + goto fail_alloc_transport; + + err = sysfs_create_group(&service->dev.kobj, &server_core_attr_group); + if (err) + goto fail_sysfs; + + return &server->state; + +fail_sysfs: + free_transport_resources(server, service); +fail_alloc_transport: + kfree(server->out_notify_map); + kfree(server->in_notify_map); +fail_init_alloc: + kfree(server); +fail: + return NULL; +} + +static void vs_core_server_release(struct vs_server_core_state *state) +{ + struct core_server *server = to_core_server(state); + struct vs_session_device *session = vs_core_server_session(server); + + /* Delete all the other services */ + vs_session_delete_noncore(session); + + sysfs_remove_group(&server->service->dev.kobj, &server_core_attr_group); + kfree(server->out_notify_map); + kfree(server->in_notify_map); + kfree(server); +} + +/** + * vs_server_create_service - create and register a new vService server + * @session: the session to create the vService server on + * @parent: an existing server that is managing the new server + * @name: the name of the new service + * @protocol: the protocol for the new service + * @plat_data: value to be assigned to (struct device *)->platform_data + */ +struct vs_service_device * +vs_server_create_service(struct vs_session_device *session, + struct vs_service_device *parent, const char *name, + const char *protocol, const void *plat_data) +{ + struct vs_service_device *core_service, *new_service; + struct core_server *server; + + if (!session->is_server || !name || !protocol) + return NULL; + + core_service = session->core_service; + if (!core_service) + return NULL; + + device_lock(&core_service->dev); + if (!core_service->dev.driver) { + device_unlock(&core_service->dev); + return NULL; + } + + server = dev_to_core_server(&core_service->dev); + + if (!parent) + parent = core_service; + + new_service = vs_server_core_create_service(server, session, parent, + VS_SERVICE_AUTO_ALLOCATE_ID, name, protocol, plat_data); + + device_unlock(&core_service->dev); + + if (IS_ERR(new_service)) + return NULL; + + return new_service; +} +EXPORT_SYMBOL(vs_server_create_service); + +/** + * vs_server_destroy_service - destroy and unregister a vService server. This + * function must _not_ be used from the target service's own workqueue. + * @service: The service to destroy + */ +int vs_server_destroy_service(struct vs_service_device *service, + struct vs_service_device *parent) +{ + struct vs_session_device *session = vs_service_get_session(service); + + if (!session->is_server || service->id == 0) + return -EINVAL; + + if (!parent) + parent = session->core_service; + + return vs_service_delete(service, parent); +} +EXPORT_SYMBOL(vs_server_destroy_service); + +static void __queue_service_created(struct vs_service_device *service, + void *data) +{ + struct core_server *server = (struct core_server *)data; + + vs_server_core_queue_service_created(server, service); +} + +static int vs_server_core_handle_connect(struct vs_server_core_state *state) +{ + struct core_server *server = to_core_server(state); + struct vs_session_device *session = vs_core_server_session(server); + int err; + + /* Tell the other end that we've finished connecting. */ + err = vs_server_core_core_send_ack_connect(state, GFP_KERNEL); + if (err) + return err; + + /* Queue a service-created message for each existing service. */ + vs_session_for_each_service(session, __queue_service_created, server); + + /* Re-enable all the services. */ + vs_session_enable_noncore(session); + + return 0; +} + +static void vs_core_server_disable_services(struct core_server *server) +{ + struct vs_session_device *session = vs_core_server_session(server); + struct pending_message *msg; + + /* Disable all the other services */ + vs_session_disable_noncore(session); + + /* Flush all the pending service-readiness messages */ + mutex_lock(&server->message_queue_lock); + while (!list_empty(&server->message_queue)) { + msg = list_first_entry(&server->message_queue, + struct pending_message, list); + vs_put_service(msg->service); + list_del(&msg->list); + kfree(msg); + } + mutex_unlock(&server->message_queue_lock); +} + +static int vs_server_core_handle_disconnect(struct vs_server_core_state *state) +{ + struct core_server *server = to_core_server(state); + + vs_core_server_disable_services(server); + + return vs_server_core_core_send_ack_disconnect(state, GFP_KERNEL); +} + +static int +vs_server_core_handle_service_reset(struct vs_server_core_state *state, + unsigned service_id) +{ + struct core_server *server = to_core_server(state); + struct vs_session_device *session = vs_core_server_session(server); + + if (service_id == 0) + return -EPROTO; + + return vs_service_handle_reset(session, service_id, false); +} + +static void vs_core_server_start(struct vs_server_core_state *state) +{ + struct core_server *server = to_core_server(state); + struct vs_session_device *session = vs_core_server_session(server); + int err; + + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &server->service->dev, + "Core server start\n"); + + err = vs_server_core_core_send_startup(&server->state, + server->service->recv_quota, + server->service->send_quota, GFP_KERNEL); + + if (err) + dev_err(&session->dev, "Failed to start core protocol: %d\n", + err); +} + +static void vs_core_server_reset(struct vs_server_core_state *state) +{ + struct core_server *server = to_core_server(state); + struct vs_session_device *session = vs_core_server_session(server); + + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &server->service->dev, + "Core server reset\n"); + + vs_core_server_disable_services(server); +} + +static struct vs_server_core vs_core_server_driver = { + .alloc = vs_core_server_alloc, + .release = vs_core_server_release, + .start = vs_core_server_start, + .reset = vs_core_server_reset, + .tx_ready = vs_core_server_tx_ready, + .core = { + .req_connect = vs_server_core_handle_connect, + .req_disconnect = vs_server_core_handle_disconnect, + .msg_service_reset = vs_server_core_handle_service_reset, + }, +}; + +/* + * Server bus driver + */ +static int vs_server_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); + + /* Don't match anything that doesn't have a protocol set yet */ + if (!service->protocol) + return 0; + + if (strcmp(service->protocol, vsdrv->protocol) == 0) + return 1; + + return 0; +} + +static int vs_server_bus_probe(struct device *dev) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_session_device *session = vs_service_get_session(service); + struct core_server *server = vs_server_session_core_server(session); + int ret; + + /* + * Set the notify counts for the service, unless the driver is the + * devio driver in which case it has already been done by the devio + * bind ioctl. The devio driver cannot be bound automatically. + */ + struct vs_service_driver *driver = + to_vs_service_driver(service->dev.driver); +#ifdef CONFIG_VSERVICES_CHAR_DEV + if (driver != &vs_devio_server_driver) +#endif + { + service->notify_recv_bits = driver->in_notify_count; + service->notify_send_bits = driver->out_notify_count; + } + + /* + * We can't allocate transport resources here for the core service + * because the resource pool doesn't exist yet. It's done in alloc() + * instead (which is called, indirectly, by vs_service_bus_probe()). + */ + if (service->id == 0) + return vs_service_bus_probe(dev); + + if (!server) + return -ENODEV; + ret = alloc_transport_resources(server, service); + if (ret < 0) + goto fail; + + ret = vs_service_bus_probe(dev); + if (ret < 0) + goto fail_free_resources; + + return 0; + +fail_free_resources: + free_transport_resources(server, service); +fail: + return ret; +} + +static int vs_server_bus_remove(struct device *dev) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_session_device *session = vs_service_get_session(service); + struct core_server *server = vs_server_session_core_server(session); + + vs_service_bus_remove(dev); + + /* + * We skip free_transport_resources for the core service because the + * resource pool has already been freed at this point. It's also + * possible that the core service has disappeared, in which case + * there's no work to do here. + */ + if (server != NULL && service->id != 0) + free_transport_resources(server, service); + + 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 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 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 ?: ""); +} + +struct service_enable_work_struct { + struct vs_service_device *service; + struct work_struct work; +}; + +static void service_enable_work(struct work_struct *work) +{ + struct service_enable_work_struct *enable_work = container_of(work, + struct service_enable_work_struct, work); + struct vs_service_device *service = enable_work->service; + struct vs_session_device *session = vs_service_get_session(service); + struct core_server *server = vs_server_session_core_server(session); + bool started; + int ret; + + kfree(enable_work); + + if (!server) + return; + /* Start and enable the service */ + vs_service_state_lock(server->service); + started = vs_service_start(service); + if (!started) { + vs_service_state_unlock(server->service); + vs_put_service(service); + return; + } + + if (VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) + vs_service_enable(service); + vs_service_state_unlock(server->service); + + /* Tell the bus to search for a driver that supports the protocol */ + ret = device_attach(&service->dev); + if (ret == 0) + dev_warn(&service->dev, "No driver found for protocol: %s\n", + service->protocol); + kobject_uevent(&service->dev.kobj, KOBJ_CHANGE); + + /* The corresponding vs_get_service was done when the work was queued */ + vs_put_service(service); +} + +static ssize_t dev_protocol_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct service_enable_work_struct *enable_work; + + /* The protocol can only be set once */ + if (service->protocol) + return -EPERM; + + /* Registering additional core servers is not allowed */ + if (strcmp(buf, VSERVICE_CORE_PROTOCOL_NAME) == 0) + return -EINVAL; + + if (strnlen(buf, VSERVICE_CORE_PROTOCOL_NAME_SIZE) + 1 > + VSERVICE_CORE_PROTOCOL_NAME_SIZE) + return -E2BIG; + + enable_work = kmalloc(sizeof(*enable_work), GFP_KERNEL); + if (!enable_work) + return -ENOMEM; + + /* Set the protocol and tell the client about it */ + service->protocol = kstrdup(buf, GFP_KERNEL); + if (!service->protocol) { + kfree(enable_work); + return -ENOMEM; + } + strim(service->protocol); + + /* + * Schedule work to enable the service. We can't do it here because + * we need to take the core service lock, and doing that here makes + * it depend circularly on this sysfs attribute, which can be deleted + * with that lock held. + * + * The corresponding vs_put_service is called in the enable_work + * function. + */ + INIT_WORK(&enable_work->work, service_enable_work); + enable_work->service = vs_get_service(service); + schedule_work(&enable_work->work); + + return count; +} + +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 ssize_t quota_in_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_session_device *session = vs_service_get_session(service); + struct core_server *server = vs_server_session_core_server(session); + int ret; + unsigned long in_quota; + + if (!server) + return -ENODEV; + /* + * Don't allow quota to be changed for services that have a driver + * bound. We take the alloc lock here because the device lock is held + * while creating and destroying this sysfs item. This means we can + * race with driver binding, but that doesn't matter: we actually just + * want to know that alloc_transport_resources() hasn't run yet, and + * that takes the alloc lock. + */ + mutex_lock(&server->alloc_lock); + if (service->dev.driver) { + ret = -EPERM; + goto out; + } + + ret = kstrtoul(buf, 0, &in_quota); + if (ret < 0) + goto out; + + service->in_quota_set = in_quota; + ret = count; + +out: + mutex_unlock(&server->alloc_lock); + + return ret; +} + +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, "%u\n", service->recv_quota); +} + +static ssize_t quota_out_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_session_device *session = vs_service_get_session(service); + struct core_server *server = vs_server_session_core_server(session); + int ret; + unsigned long out_quota; + + if (!server) + return -ENODEV; + /* See comment in quota_in_store. */ + mutex_lock(&server->alloc_lock); + if (service->dev.driver) { + ret = -EPERM; + goto out; + } + + ret = kstrtoul(buf, 0, &out_quota); + if (ret < 0) + goto out; + + service->out_quota_set = out_quota; + ret = count; + +out: + mutex_unlock(&server->alloc_lock); + + return ret; +} + +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, "%u\n", service->send_quota); +} + +static struct device_attribute vs_server_dev_attrs[] = { + __ATTR_RO(id), + __ATTR_RO(is_server), + __ATTR(protocol, S_IRUGO | S_IWUSR, + dev_protocol_show, dev_protocol_store), + __ATTR_RO(service_name), + __ATTR(quota_in, S_IRUGO | S_IWUSR, + quota_in_show, quota_in_store), + __ATTR(quota_out, S_IRUGO | S_IWUSR, + quota_out_show, quota_out_store), + __ATTR_NULL +}; + +static ssize_t protocol_show(struct device_driver *drv, char *buf) +{ + struct vs_service_driver *vsdrv = to_vs_service_driver(drv); + + return scnprintf(buf, PAGE_SIZE, "%s\n", vsdrv->protocol); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) +static struct driver_attribute vs_server_drv_attrs[] = { + __ATTR_RO(protocol), + __ATTR_NULL +}; +#else +static DRIVER_ATTR_RO(protocol); + +static struct attribute *vs_server_drv_attrs[] = { + &driver_attr_protocol.attr, + NULL, +}; +ATTRIBUTE_GROUPS(vs_server_drv); +#endif + +struct bus_type vs_server_bus_type = { + .name = "vservices-server", + .dev_attrs = vs_server_dev_attrs, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) + .drv_attrs = vs_server_drv_attrs, +#else + .drv_groups = vs_server_drv_groups, +#endif + .match = vs_server_bus_match, + .probe = vs_server_bus_probe, + .remove = vs_server_bus_remove, + .uevent = vs_service_bus_uevent, +}; +EXPORT_SYMBOL(vs_server_bus_type); + +/* + * Server session driver + */ +static int vs_server_session_probe(struct device *dev) +{ + struct vs_session_device *session = to_vs_session_device(dev); + struct vs_service_device *service; + + service = __vs_server_core_register_service(session, 0, NULL, + VSERVICE_CORE_SERVICE_NAME, + VSERVICE_CORE_PROTOCOL_NAME, NULL); + + return PTR_ERR_OR_ZERO(service); +} + +static int +vs_server_session_service_added(struct vs_session_device *session, + struct vs_service_device *service) +{ + struct core_server *server = vs_server_session_core_server(session); + int err; + + if (WARN_ON(!server || !service->id)) + return -EINVAL; + + err = vs_server_core_queue_service_created(server, service); + + if (err) + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev, + "failed to send service_created: %d\n", err); + + return err; +} + +static int +vs_server_session_service_start(struct vs_session_device *session, + struct vs_service_device *service) +{ + struct core_server *server = vs_server_session_core_server(session); + int err; + + if (WARN_ON(!server || !service->id)) + return -EINVAL; + + err = vs_server_core_queue_service_reset_ready(server, + VSERVICE_CORE_CORE_MSG_SERVER_READY, service); + + if (err) + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev, + "failed to send server_ready: %d\n", err); + + return err; +} + +static int +vs_server_session_service_local_reset(struct vs_session_device *session, + struct vs_service_device *service) +{ + struct core_server *server = vs_server_session_core_server(session); + int err; + + if (WARN_ON(!server || !service->id)) + return -EINVAL; + + err = vs_server_core_queue_service_reset_ready(server, + VSERVICE_CORE_CORE_MSG_SERVICE_RESET, service); + + if (err) + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev, + "failed to send service_reset: %d\n", err); + + return err; +} + +static int +vs_server_session_service_removed(struct vs_session_device *session, + struct vs_service_device *service) +{ + struct core_server *server = vs_server_session_core_server(session); + int err; + + /* + * It's possible for the core server to be forcibly removed before + * the other services, for example when the underlying transport + * vanishes. If that happens, we can end up here with a NULL core + * server pointer. + */ + if (!server) + return 0; + + if (WARN_ON(!service->id)) + return -EINVAL; + + err = vs_server_core_queue_service_removed(server, service); + if (err) + vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev, + "failed to send service_removed: %d\n", err); + + return err; +} + +static struct vs_session_driver vs_server_session_driver = { + .driver = { + .name = "vservices-server-session", + .owner = THIS_MODULE, + .bus = &vs_session_bus_type, + .probe = vs_server_session_probe, + .suppress_bind_attrs = true, + }, + .is_server = true, + .service_bus = &vs_server_bus_type, + .service_added = vs_server_session_service_added, + .service_start = vs_server_session_service_start, + .service_local_reset = vs_server_session_service_local_reset, + .service_removed = vs_server_session_service_removed, +}; + +static int __init vs_core_server_init(void) +{ + int ret; + + ret = bus_register(&vs_server_bus_type); + if (ret) + goto fail_bus_register; + +#ifdef CONFIG_VSERVICES_CHAR_DEV + vs_devio_server_driver.driver.bus = &vs_server_bus_type; + vs_devio_server_driver.driver.owner = THIS_MODULE; + ret = driver_register(&vs_devio_server_driver.driver); + if (ret) + goto fail_devio_register; +#endif + + ret = driver_register(&vs_server_session_driver.driver); + if (ret) + goto fail_driver_register; + + ret = vservice_core_server_register(&vs_core_server_driver, + "vs_core_server"); + if (ret) + goto fail_core_register; + + vservices_server_root = kobject_create_and_add("server-sessions", + vservices_root); + if (!vservices_server_root) { + ret = -ENOMEM; + goto fail_create_root; + } + + return 0; + +fail_create_root: + vservice_core_server_unregister(&vs_core_server_driver); +fail_core_register: + driver_unregister(&vs_server_session_driver.driver); +fail_driver_register: +#ifdef CONFIG_VSERVICES_CHAR_DEV + driver_unregister(&vs_devio_server_driver.driver); + vs_devio_server_driver.driver.bus = NULL; + vs_devio_server_driver.driver.owner = NULL; +fail_devio_register: +#endif + bus_unregister(&vs_server_bus_type); +fail_bus_register: + return ret; +} + +static void __exit vs_core_server_exit(void) +{ + kobject_put(vservices_server_root); + vservice_core_server_unregister(&vs_core_server_driver); + driver_unregister(&vs_server_session_driver.driver); +#ifdef CONFIG_VSERVICES_CHAR_DEV + driver_unregister(&vs_devio_server_driver.driver); + vs_devio_server_driver.driver.bus = NULL; + vs_devio_server_driver.driver.owner = NULL; +#endif + bus_unregister(&vs_server_bus_type); +} + +subsys_initcall(vs_core_server_init); +module_exit(vs_core_server_exit); + +MODULE_DESCRIPTION("OKL4 Virtual Services Core Server Driver"); +MODULE_AUTHOR("Open Kernel Labs, Inc"); diff --git a/drivers/vservices/debug.h b/drivers/vservices/debug.h new file mode 100644 index 000000000000..b379b04942d3 --- /dev/null +++ b/drivers/vservices/debug.h @@ -0,0 +1,74 @@ +/* + * drivers/vservices/debug.h + * + * 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 and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * Debugging macros and support functions for Virtual Services. + */ +#ifndef _VSERVICES_DEBUG_H +#define _VSERVICES_DEBUG_H + +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38) +#include +#else +#ifndef no_printk +#define no_printk(format, args...) do { } while (0) +#endif +#endif + +#include +#include "transport.h" + +#define VS_DEBUG_TRANSPORT (1 << 0) +#define VS_DEBUG_TRANSPORT_MESSAGES (1 << 1) +#define VS_DEBUG_SESSION (1 << 2) +#define VS_DEBUG_CLIENT (1 << 3) +#define VS_DEBUG_CLIENT_CORE (1 << 4) +#define VS_DEBUG_SERVER (1 << 5) +#define VS_DEBUG_SERVER_CORE (1 << 6) +#define VS_DEBUG_PROTOCOL (1 << 7) +#define VS_DEBUG_ALL 0xff + +#ifdef CONFIG_VSERVICES_DEBUG + +#define vs_debug(type, session, format, args...) \ + do { \ + if ((session)->debug_mask & (type)) \ + dev_dbg(&(session)->dev, format, ##args); \ + } while (0) + +#define vs_dev_debug(type, session, dev, format, args...) \ + do { \ + if ((session)->debug_mask & (type)) \ + dev_dbg(dev, format, ##args); \ + } while (0) + +static inline void vs_debug_dump_mbuf(struct vs_session_device *session, + struct vs_mbuf *mbuf) +{ + if (session->debug_mask & VS_DEBUG_TRANSPORT_MESSAGES) + print_hex_dump_bytes("msg:", DUMP_PREFIX_OFFSET, + mbuf->data, mbuf->size); +} + +#else + +/* Dummy versions: Use no_printk to retain type/format string checking */ +#define vs_debug(type, session, format, args...) \ + do { (void)session; no_printk(format, ##args); } while(0) + +#define vs_dev_debug(type, session, dev, format, args...) \ + do { (void)session; (void)dev; no_printk(format, ##args); } while(0) + +static inline void vs_debug_dump_mbuf(struct vs_session_device *session, + struct vs_mbuf *mbuf) {} + +#endif /* CONFIG_VSERVICES_DEBUG */ + +#endif /* _VSERVICES_DEBUG_H */ diff --git a/drivers/vservices/devio.c b/drivers/vservices/devio.c new file mode 100644 index 000000000000..b3ed4ab7d1d6 --- /dev/null +++ b/drivers/vservices/devio.c @@ -0,0 +1,1059 @@ +/* + * devio.c - cdev I/O for service devices + * + * Copyright (c) 2016 Cog Systems Pty Ltd + * Author: Philip Derrin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "session.h" + +#define VSERVICES_DEVICE_MAX (VS_MAX_SERVICES * VS_MAX_SESSIONS) + +struct vs_devio_priv { + struct kref kref; + bool running, reset; + + /* Receive queue */ + wait_queue_head_t recv_wq; + atomic_t notify_pending; + struct list_head recv_queue; +}; + +static void +vs_devio_priv_free(struct kref *kref) +{ + struct vs_devio_priv *priv = container_of(kref, struct vs_devio_priv, + kref); + + WARN_ON(priv->running); + WARN_ON(!list_empty_careful(&priv->recv_queue)); + WARN_ON(waitqueue_active(&priv->recv_wq)); + + kfree(priv); +} + +static void vs_devio_priv_put(struct vs_devio_priv *priv) +{ + kref_put(&priv->kref, vs_devio_priv_free); +} + +static int +vs_devio_service_probe(struct vs_service_device *service) +{ + struct vs_devio_priv *priv; + + priv = kmalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + kref_init(&priv->kref); + priv->running = false; + priv->reset = false; + init_waitqueue_head(&priv->recv_wq); + atomic_set(&priv->notify_pending, 0); + INIT_LIST_HEAD(&priv->recv_queue); + + dev_set_drvdata(&service->dev, priv); + + wake_up(&service->quota_wq); + + return 0; +} + +static int +vs_devio_service_remove(struct vs_service_device *service) +{ + struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); + + WARN_ON(priv->running); + WARN_ON(!list_empty_careful(&priv->recv_queue)); + WARN_ON(waitqueue_active(&priv->recv_wq)); + + vs_devio_priv_put(priv); + + return 0; +} + +static int +vs_devio_service_receive(struct vs_service_device *service, + struct vs_mbuf *mbuf) +{ + struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); + + WARN_ON(!priv->running); + + spin_lock(&priv->recv_wq.lock); + list_add_tail(&mbuf->queue, &priv->recv_queue); + wake_up_locked(&priv->recv_wq); + spin_unlock(&priv->recv_wq.lock); + + return 0; +} + +static void +vs_devio_service_notify(struct vs_service_device *service, u32 flags) +{ + struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); + int old, cur; + + WARN_ON(!priv->running); + + if (!flags) + return; + + /* open-coded atomic_or() */ + cur = atomic_read(&priv->notify_pending); + while ((old = atomic_cmpxchg(&priv->notify_pending, + cur, cur | flags)) != cur) + cur = old; + + wake_up(&priv->recv_wq); +} + +static void +vs_devio_service_start(struct vs_service_device *service) +{ + struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); + + if (!priv->reset) { + WARN_ON(priv->running); + priv->running = true; + wake_up(&service->quota_wq); + } +} + +static void +vs_devio_service_reset(struct vs_service_device *service) +{ + struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); + struct vs_mbuf *mbuf, *tmp; + + WARN_ON(!priv->running && !priv->reset); + + /* + * Mark the service as being in reset. This flag can never be cleared + * on an open device; the user must acknowledge the reset by closing + * and reopening the device. + */ + priv->reset = true; + priv->running = false; + + spin_lock_irq(&priv->recv_wq.lock); + list_for_each_entry_safe(mbuf, tmp, &priv->recv_queue, queue) + vs_service_free_mbuf(service, mbuf); + INIT_LIST_HEAD(&priv->recv_queue); + spin_unlock_irq(&priv->recv_wq.lock); + wake_up_all(&priv->recv_wq); +} + +/* + * This driver will be registered by the core server module, which must also + * set its bus and owner function pointers. + */ +struct vs_service_driver vs_devio_server_driver = { + /* No protocol, so the normal bus match will never bind this. */ + .protocol = NULL, + .is_server = true, + .rx_atomic = true, + + .probe = vs_devio_service_probe, + .remove = vs_devio_service_remove, + .receive = vs_devio_service_receive, + .notify = vs_devio_service_notify, + .start = vs_devio_service_start, + .reset = vs_devio_service_reset, + + /* + * Set reasonable default quotas. These can be overridden by passing + * nonzero values to IOCTL_VS_BIND_SERVER, which will set the + * service's *_quota_set fields. + */ + .in_quota_min = 1, + .in_quota_best = 8, + .out_quota_min = 1, + .out_quota_best = 8, + + /* Mark the notify counts as invalid; the service's will be used. */ + .in_notify_count = (unsigned)-1, + .out_notify_count = (unsigned)-1, + + .driver = { + .name = "vservices-server-devio", + .owner = NULL, /* set by core server */ + .bus = NULL, /* set by core server */ + .suppress_bind_attrs = true, /* see vs_devio_poll */ + }, +}; +EXPORT_SYMBOL_GPL(vs_devio_server_driver); + +static int +vs_devio_bind_server(struct vs_service_device *service, + struct vs_ioctl_bind *bind) +{ + int ret = -ENODEV; + + /* Ensure the server module is loaded and the driver is registered. */ + if (!try_module_get(vs_devio_server_driver.driver.owner)) + goto fail_module_get; + + device_lock(&service->dev); + ret = -EBUSY; + if (service->dev.driver != NULL) + goto fail_device_unbound; + + /* Set up the quota and notify counts. */ + service->in_quota_set = bind->recv_quota; + service->out_quota_set = bind->send_quota; + service->notify_send_bits = bind->send_notify_bits; + service->notify_recv_bits = bind->recv_notify_bits; + + /* Manually probe the driver. */ + service->dev.driver = &vs_devio_server_driver.driver; + ret = service->dev.bus->probe(&service->dev); + if (ret < 0) + goto fail_probe_driver; + + ret = device_bind_driver(&service->dev); + if (ret < 0) + goto fail_bind_driver; + + /* Pass the allocated quotas back to the user. */ + bind->recv_quota = service->recv_quota; + bind->send_quota = service->send_quota; + bind->msg_size = vs_service_max_mbuf_size(service); + + device_unlock(&service->dev); + module_put(vs_devio_server_driver.driver.owner); + + return 0; + +fail_bind_driver: + ret = service->dev.bus->remove(&service->dev); +fail_probe_driver: + service->dev.driver = NULL; +fail_device_unbound: + device_unlock(&service->dev); + module_put(vs_devio_server_driver.driver.owner); +fail_module_get: + return ret; +} + +/* + * This driver will be registered by the core client module, which must also + * set its bus and owner pointers. + */ +struct vs_service_driver vs_devio_client_driver = { + /* No protocol, so the normal bus match will never bind this. */ + .protocol = NULL, + .is_server = false, + .rx_atomic = true, + + .probe = vs_devio_service_probe, + .remove = vs_devio_service_remove, + .receive = vs_devio_service_receive, + .notify = vs_devio_service_notify, + .start = vs_devio_service_start, + .reset = vs_devio_service_reset, + + .driver = { + .name = "vservices-client-devio", + .owner = NULL, /* set by core client */ + .bus = NULL, /* set by core client */ + .suppress_bind_attrs = true, /* see vs_devio_poll */ + }, +}; +EXPORT_SYMBOL_GPL(vs_devio_client_driver); + +static int +vs_devio_bind_client(struct vs_service_device *service, + struct vs_ioctl_bind *bind) +{ + int ret = -ENODEV; + + /* Ensure the client module is loaded and the driver is registered. */ + if (!try_module_get(vs_devio_client_driver.driver.owner)) + goto fail_module_get; + + device_lock(&service->dev); + ret = -EBUSY; + if (service->dev.driver != NULL) + goto fail_device_unbound; + + /* Manually probe the driver. */ + service->dev.driver = &vs_devio_client_driver.driver; + ret = service->dev.bus->probe(&service->dev); + if (ret < 0) + goto fail_probe_driver; + + ret = device_bind_driver(&service->dev); + if (ret < 0) + goto fail_bind_driver; + + /* Pass the allocated quotas back to the user. */ + bind->recv_quota = service->recv_quota; + bind->send_quota = service->send_quota; + bind->msg_size = vs_service_max_mbuf_size(service); + bind->send_notify_bits = service->notify_send_bits; + bind->recv_notify_bits = service->notify_recv_bits; + + device_unlock(&service->dev); + module_put(vs_devio_client_driver.driver.owner); + + return 0; + +fail_bind_driver: + ret = service->dev.bus->remove(&service->dev); +fail_probe_driver: + service->dev.driver = NULL; +fail_device_unbound: + device_unlock(&service->dev); + module_put(vs_devio_client_driver.driver.owner); +fail_module_get: + return ret; +} + +static struct vs_devio_priv * +vs_devio_priv_get_from_service(struct vs_service_device *service) +{ + struct vs_devio_priv *priv = NULL; + struct device_driver *drv; + + if (!service) + return NULL; + + device_lock(&service->dev); + drv = service->dev.driver; + + if ((drv == &vs_devio_client_driver.driver) || + (drv == &vs_devio_server_driver.driver)) { + vs_service_state_lock(service); + priv = dev_get_drvdata(&service->dev); + if (priv) + kref_get(&priv->kref); + vs_service_state_unlock(service); + } + + device_unlock(&service->dev); + + return priv; +} + +static int +vs_devio_open(struct inode *inode, struct file *file) +{ + struct vs_service_device *service; + + if (imajor(inode) != vservices_cdev_major) + return -ENODEV; + + service = vs_service_lookup_by_devt(inode->i_rdev); + if (!service) + return -ENODEV; + + file->private_data = service; + + return 0; +} + +static int +vs_devio_release(struct inode *inode, struct file *file) +{ + struct vs_service_device *service = file->private_data; + + if (service) { + struct vs_devio_priv *priv = + vs_devio_priv_get_from_service(service); + + if (priv) { + device_release_driver(&service->dev); + vs_devio_priv_put(priv); + } + + file->private_data = NULL; + vs_put_service(service); + } + + return 0; +} + +static struct iovec * +vs_devio_check_iov(struct vs_ioctl_iovec *io, bool is_send, ssize_t *total) +{ + struct iovec *iov; + unsigned i; + int ret; + + if (io->iovcnt > UIO_MAXIOV) + return ERR_PTR(-EINVAL); + + iov = kmalloc(sizeof(*iov) * io->iovcnt, GFP_KERNEL); + if (!iov) + return ERR_PTR(-ENOMEM); + + if (copy_from_user(iov, io->iov, sizeof(*iov) * io->iovcnt)) { + ret = -EFAULT; + goto fail; + } + + *total = 0; + for (i = 0; i < io->iovcnt; i++) { + ssize_t iov_len = (ssize_t)iov[i].iov_len; + + if (iov_len > MAX_RW_COUNT - *total) { + ret = -EINVAL; + goto fail; + } + + if (!access_ok(is_send ? VERIFY_READ : VERIFY_WRITE, + iov[i].iov_base, iov_len)) { + ret = -EFAULT; + goto fail; + } + + *total += iov_len; + } + + return iov; + +fail: + kfree(iov); + return ERR_PTR(ret); +} + +static ssize_t +vs_devio_send(struct vs_service_device *service, struct iovec *iov, + size_t iovcnt, ssize_t to_send, bool nonblocking) +{ + struct vs_mbuf *mbuf = NULL; + struct vs_devio_priv *priv; + unsigned i; + ssize_t offset = 0; + ssize_t ret; + DEFINE_WAIT(wait); + + priv = vs_devio_priv_get_from_service(service); + ret = -ENODEV; + if (!priv) + goto fail_priv_get; + + vs_service_state_lock(service); + + /* + * Waiting alloc. We must open-code this because there is no real + * state structure or base state. + */ + ret = 0; + while (!vs_service_send_mbufs_available(service)) { + if (nonblocking) { + ret = -EAGAIN; + break; + } + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + prepare_to_wait_exclusive(&service->quota_wq, &wait, + TASK_INTERRUPTIBLE); + + vs_service_state_unlock(service); + schedule(); + vs_service_state_lock(service); + + if (priv->reset) { + ret = -ECONNRESET; + break; + } + + if (!priv->running) { + ret = -ENOTCONN; + break; + } + } + finish_wait(&service->quota_wq, &wait); + + if (ret) + goto fail_alloc; + + mbuf = vs_service_alloc_mbuf(service, to_send, GFP_KERNEL); + if (IS_ERR(mbuf)) { + ret = PTR_ERR(mbuf); + goto fail_alloc; + } + + /* Ready to send; copy data into the mbuf. */ + ret = -EFAULT; + for (i = 0; i < iovcnt; i++) { + if (copy_from_user(mbuf->data + offset, iov[i].iov_base, + iov[i].iov_len)) + goto fail_copy; + offset += iov[i].iov_len; + } + mbuf->size = to_send; + + /* Send the message. */ + ret = vs_service_send(service, mbuf); + if (ret < 0) + goto fail_send; + + /* Wake the next waiter, if there's more quota available. */ + if (waitqueue_active(&service->quota_wq) && + vs_service_send_mbufs_available(service) > 0) + wake_up(&service->quota_wq); + + vs_service_state_unlock(service); + vs_devio_priv_put(priv); + + return to_send; + +fail_send: +fail_copy: + vs_service_free_mbuf(service, mbuf); + wake_up(&service->quota_wq); +fail_alloc: + vs_service_state_unlock(service); + vs_devio_priv_put(priv); +fail_priv_get: + return ret; +} + +static ssize_t +vs_devio_recv(struct vs_service_device *service, struct iovec *iov, + size_t iovcnt, u32 *notify_bits, ssize_t recv_space, + bool nonblocking) +{ + struct vs_mbuf *mbuf = NULL; + struct vs_devio_priv *priv; + unsigned i; + ssize_t offset = 0; + ssize_t ret; + DEFINE_WAIT(wait); + + priv = vs_devio_priv_get_from_service(service); + ret = -ENODEV; + if (!priv) + goto fail_priv_get; + + /* Take the recv_wq lock, which also protects recv_queue. */ + spin_lock_irq(&priv->recv_wq.lock); + + /* Wait for a message, notification, or reset. */ + ret = wait_event_interruptible_exclusive_locked_irq(priv->recv_wq, + !list_empty(&priv->recv_queue) || priv->reset || + atomic_read(&priv->notify_pending) || nonblocking); + + if (priv->reset) + ret = -ECONNRESET; /* Service reset */ + else if (!ret && list_empty(&priv->recv_queue)) + ret = -EAGAIN; /* Nonblocking, or notification */ + + if (ret < 0) { + spin_unlock_irq(&priv->recv_wq.lock); + goto no_mbuf; + } + + /* Take the first mbuf from the list, and check its size. */ + mbuf = list_first_entry(&priv->recv_queue, struct vs_mbuf, queue); + if (mbuf->size > recv_space) { + spin_unlock_irq(&priv->recv_wq.lock); + ret = -EMSGSIZE; + goto fail_msg_size; + } + list_del_init(&mbuf->queue); + + spin_unlock_irq(&priv->recv_wq.lock); + + /* Copy to user. */ + ret = -EFAULT; + for (i = 0; (mbuf->size > offset) && (i < iovcnt); i++) { + size_t len = min(mbuf->size - offset, iov[i].iov_len); + if (copy_to_user(iov[i].iov_base, mbuf->data + offset, len)) + goto fail_copy; + offset += len; + } + ret = offset; + +no_mbuf: + /* + * Read and clear the pending notification bits. If any notifications + * are received, don't return an error, even if we failed to receive a + * message. + */ + *notify_bits = atomic_xchg(&priv->notify_pending, 0); + if ((ret < 0) && *notify_bits) + ret = 0; + +fail_copy: + if (mbuf) + vs_service_free_mbuf(service, mbuf); +fail_msg_size: + vs_devio_priv_put(priv); +fail_priv_get: + return ret; +} + +static int +vs_devio_check_perms(struct file *file, unsigned flags) +{ + if ((flags & MAY_READ) & !(file->f_mode & FMODE_READ)) + return -EBADF; + + if ((flags & MAY_WRITE) & !(file->f_mode & FMODE_WRITE)) + return -EBADF; + + return security_file_permission(file, flags); +} + +static long +vs_devio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *ptr = (void __user *)arg; + struct vs_service_device *service = file->private_data; + struct vs_ioctl_bind bind; + struct vs_ioctl_iovec io; + u32 flags; + long ret; + ssize_t iov_total; + struct iovec *iov; + + if (!service) + return -ENODEV; + + switch (cmd) { + case IOCTL_VS_RESET_SERVICE: + ret = vs_devio_check_perms(file, MAY_WRITE); + if (ret < 0) + break; + ret = vs_service_reset(service, service); + break; + case IOCTL_VS_GET_NAME: + ret = vs_devio_check_perms(file, MAY_READ); + if (ret < 0) + break; + if (service->name != NULL) { + size_t len = strnlen(service->name, + _IOC_SIZE(IOCTL_VS_GET_NAME) - 1); + if (copy_to_user(ptr, service->name, len + 1)) + ret = -EFAULT; + } else { + ret = -EINVAL; + } + break; + case IOCTL_VS_GET_PROTOCOL: + ret = vs_devio_check_perms(file, MAY_READ); + if (ret < 0) + break; + if (service->protocol != NULL) { + size_t len = strnlen(service->protocol, + _IOC_SIZE(IOCTL_VS_GET_PROTOCOL) - 1); + if (copy_to_user(ptr, service->protocol, len + 1)) + ret = -EFAULT; + } else { + ret = -EINVAL; + } + break; + case IOCTL_VS_BIND_CLIENT: + ret = vs_devio_check_perms(file, MAY_EXEC); + if (ret < 0) + break; + ret = vs_devio_bind_client(service, &bind); + if (!ret && copy_to_user(ptr, &bind, sizeof(bind))) + ret = -EFAULT; + break; + case IOCTL_VS_BIND_SERVER: + ret = vs_devio_check_perms(file, MAY_EXEC); + if (ret < 0) + break; + if (copy_from_user(&bind, ptr, sizeof(bind))) { + ret = -EFAULT; + break; + } + ret = vs_devio_bind_server(service, &bind); + if (!ret && copy_to_user(ptr, &bind, sizeof(bind))) + ret = -EFAULT; + break; + case IOCTL_VS_NOTIFY: + ret = vs_devio_check_perms(file, MAY_WRITE); + if (ret < 0) + break; + if (copy_from_user(&flags, ptr, sizeof(flags))) { + ret = -EFAULT; + break; + } + ret = vs_service_notify(service, flags); + break; + case IOCTL_VS_SEND: + ret = vs_devio_check_perms(file, MAY_WRITE); + if (ret < 0) + break; + if (copy_from_user(&io, ptr, sizeof(io))) { + ret = -EFAULT; + break; + } + + iov = vs_devio_check_iov(&io, true, &iov_total); + if (IS_ERR(iov)) { + ret = PTR_ERR(iov); + break; + } + + ret = vs_devio_send(service, iov, io.iovcnt, iov_total, + file->f_flags & O_NONBLOCK); + kfree(iov); + break; + case IOCTL_VS_RECV: + ret = vs_devio_check_perms(file, MAY_READ); + if (ret < 0) + break; + if (copy_from_user(&io, ptr, sizeof(io))) { + ret = -EFAULT; + break; + } + + iov = vs_devio_check_iov(&io, true, &iov_total); + if (IS_ERR(iov)) { + ret = PTR_ERR(iov); + break; + } + + ret = vs_devio_recv(service, iov, io.iovcnt, + &io.notify_bits, iov_total, + file->f_flags & O_NONBLOCK); + kfree(iov); + + if (ret >= 0) { + u32 __user *notify_bits_ptr = ptr + offsetof( + struct vs_ioctl_iovec, notify_bits); + if (copy_to_user(notify_bits_ptr, &io.notify_bits, + sizeof(io.notify_bits))) + ret = -EFAULT; + } + break; + default: + dev_dbg(&service->dev, "Unknown ioctl %#x, arg: %lx\n", cmd, + arg); + ret = -ENOSYS; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT + +struct vs_compat_ioctl_bind { + __u32 send_quota; + __u32 recv_quota; + __u32 send_notify_bits; + __u32 recv_notify_bits; + compat_size_t msg_size; +}; + +#define compat_ioctl_bind_conv(dest, src) ({ \ + dest.send_quota = src.send_quota; \ + dest.recv_quota = src.recv_quota; \ + dest.send_notify_bits = src.send_notify_bits; \ + dest.recv_notify_bits = src.recv_notify_bits; \ + dest.msg_size = (compat_size_t)src.msg_size; \ +}) + +#define COMPAT_IOCTL_VS_BIND_CLIENT _IOR('4', 3, struct vs_compat_ioctl_bind) +#define COMPAT_IOCTL_VS_BIND_SERVER _IOWR('4', 4, struct vs_compat_ioctl_bind) + +struct vs_compat_ioctl_iovec { + union { + __u32 iovcnt; /* input */ + __u32 notify_bits; /* output (recv only) */ + }; + compat_uptr_t iov; +}; + +#define COMPAT_IOCTL_VS_SEND \ + _IOW('4', 6, struct vs_compat_ioctl_iovec) +#define COMPAT_IOCTL_VS_RECV \ + _IOWR('4', 7, struct vs_compat_ioctl_iovec) + +static struct iovec * +vs_devio_check_compat_iov(struct vs_compat_ioctl_iovec *c_io, + bool is_send, ssize_t *total) +{ + struct iovec *iov; + struct compat_iovec *c_iov; + + unsigned i; + int ret; + + if (c_io->iovcnt > UIO_MAXIOV) + return ERR_PTR(-EINVAL); + + c_iov = kzalloc(sizeof(*c_iov) * c_io->iovcnt, GFP_KERNEL); + if (!c_iov) + return ERR_PTR(-ENOMEM); + + iov = kzalloc(sizeof(*iov) * c_io->iovcnt, GFP_KERNEL); + if (!iov) { + kfree(c_iov); + return ERR_PTR(-ENOMEM); + } + + if (copy_from_user(c_iov, (struct compat_iovec __user *) + compat_ptr(c_io->iov), sizeof(*c_iov) * c_io->iovcnt)) { + ret = -EFAULT; + goto fail; + } + + *total = 0; + for (i = 0; i < c_io->iovcnt; i++) { + ssize_t iov_len; + iov[i].iov_base = compat_ptr (c_iov[i].iov_base); + iov[i].iov_len = (compat_size_t) c_iov[i].iov_len; + + iov_len = (ssize_t)iov[i].iov_len; + + if (iov_len > MAX_RW_COUNT - *total) { + ret = -EINVAL; + goto fail; + } + + if (!access_ok(is_send ? VERIFY_READ : VERIFY_WRITE, + iov[i].iov_base, iov_len)) { + ret = -EFAULT; + goto fail; + } + + *total += iov_len; + } + + kfree (c_iov); + return iov; + +fail: + kfree(c_iov); + kfree(iov); + return ERR_PTR(ret); +} + +static long +vs_devio_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *ptr = (void __user *)arg; + struct vs_service_device *service = file->private_data; + struct vs_ioctl_bind bind; + struct vs_compat_ioctl_bind compat_bind; + struct vs_compat_ioctl_iovec compat_io; + long ret; + ssize_t iov_total; + struct iovec *iov; + + if (!service) + return -ENODEV; + + switch (cmd) { + case IOCTL_VS_RESET_SERVICE: + case IOCTL_VS_GET_NAME: + case IOCTL_VS_GET_PROTOCOL: + return vs_devio_ioctl (file, cmd, arg); + case COMPAT_IOCTL_VS_SEND: + ret = vs_devio_check_perms(file, MAY_WRITE); + if (ret < 0) + break; + if (copy_from_user(&compat_io, ptr, sizeof(compat_io))) { + ret = -EFAULT; + break; + } + + iov = vs_devio_check_compat_iov(&compat_io, true, &iov_total); + if (IS_ERR(iov)) { + ret = PTR_ERR(iov); + break; + } + + ret = vs_devio_send(service, iov, compat_io.iovcnt, iov_total, + file->f_flags & O_NONBLOCK); + kfree(iov); + + break; + case COMPAT_IOCTL_VS_RECV: + ret = vs_devio_check_perms(file, MAY_READ); + if (ret < 0) + break; + if (copy_from_user(&compat_io, ptr, sizeof(compat_io))) { + ret = -EFAULT; + break; + } + + iov = vs_devio_check_compat_iov(&compat_io, true, &iov_total); + if (IS_ERR(iov)) { + ret = PTR_ERR(iov); + break; + } + + ret = vs_devio_recv(service, iov, compat_io.iovcnt, + &compat_io.notify_bits, iov_total, + file->f_flags & O_NONBLOCK); + kfree(iov); + + if (ret >= 0) { + u32 __user *notify_bits_ptr = ptr + offsetof( + struct vs_compat_ioctl_iovec, notify_bits); + if (copy_to_user(notify_bits_ptr, &compat_io.notify_bits, + sizeof(compat_io.notify_bits))) + ret = -EFAULT; + } + break; + case COMPAT_IOCTL_VS_BIND_CLIENT: + ret = vs_devio_check_perms(file, MAY_EXEC); + if (ret < 0) + break; + ret = vs_devio_bind_client(service, &bind); + compat_ioctl_bind_conv(compat_bind, bind); + if (!ret && copy_to_user(ptr, &compat_bind, + sizeof(compat_bind))) + ret = -EFAULT; + break; + case COMPAT_IOCTL_VS_BIND_SERVER: + ret = vs_devio_check_perms(file, MAY_EXEC); + if (ret < 0) + break; + if (copy_from_user(&compat_bind, ptr, sizeof(compat_bind))) { + ret = -EFAULT; + break; + } + compat_ioctl_bind_conv(bind, compat_bind); + ret = vs_devio_bind_server(service, &bind); + compat_ioctl_bind_conv(compat_bind, bind); + if (!ret && copy_to_user(ptr, &compat_bind, + sizeof(compat_bind))) + ret = -EFAULT; + break; + default: + dev_dbg(&service->dev, "Unknown ioctl %#x, arg: %lx\n", cmd, + arg); + ret = -ENOSYS; + break; + } + + return ret; +} + +#endif /* CONFIG_COMPAT */ + +static unsigned int +vs_devio_poll(struct file *file, struct poll_table_struct *wait) +{ + struct vs_service_device *service = file->private_data; + struct vs_devio_priv *priv = vs_devio_priv_get_from_service(service); + unsigned int flags = 0; + + poll_wait(file, &service->quota_wq, wait); + + if (priv) { + /* + * Note: there is no way for us to ensure that all poll + * waiters on a given workqueue have gone away, other than to + * actually close the file. So, this poll_wait() is only safe + * if we never release our claim on the service before the + * file is closed. + * + * We try to guarantee this by only unbinding the devio driver + * on close, and setting suppress_bind_attrs in the driver so + * root can't unbind us with sysfs. + */ + poll_wait(file, &priv->recv_wq, wait); + + if (priv->reset) { + /* Service reset; raise poll error. */ + flags |= POLLERR | POLLHUP; + } else if (priv->running) { + if (!list_empty_careful(&priv->recv_queue)) + flags |= POLLRDNORM | POLLIN; + if (atomic_read(&priv->notify_pending)) + flags |= POLLRDNORM | POLLIN; + if (vs_service_send_mbufs_available(service) > 0) + flags |= POLLWRNORM | POLLOUT; + } + + vs_devio_priv_put(priv); + } else { + /* No driver attached. Return error flags. */ + flags |= POLLERR | POLLHUP; + } + + return flags; +} + +static const struct file_operations vs_fops = { + .owner = THIS_MODULE, + .open = vs_devio_open, + .release = vs_devio_release, + .unlocked_ioctl = vs_devio_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vs_devio_compat_ioctl, +#endif + .poll = vs_devio_poll, +}; + +int vservices_cdev_major; +static struct cdev vs_cdev; + +int __init +vs_devio_init(void) +{ + dev_t dev; + int r; + + r = alloc_chrdev_region(&dev, 0, VSERVICES_DEVICE_MAX, + "vs_service"); + if (r < 0) + goto fail_alloc_chrdev; + vservices_cdev_major = MAJOR(dev); + + cdev_init(&vs_cdev, &vs_fops); + r = cdev_add(&vs_cdev, dev, VSERVICES_DEVICE_MAX); + if (r < 0) + goto fail_cdev_add; + + return 0; + +fail_cdev_add: + unregister_chrdev_region(dev, VSERVICES_DEVICE_MAX); +fail_alloc_chrdev: + return r; +} + +void __exit +vs_devio_exit(void) +{ + cdev_del(&vs_cdev); + unregister_chrdev_region(MKDEV(vservices_cdev_major, 0), + VSERVICES_DEVICE_MAX); +} diff --git a/drivers/vservices/protocol/Kconfig b/drivers/vservices/protocol/Kconfig new file mode 100644 index 000000000000..a4c622bee422 --- /dev/null +++ b/drivers/vservices/protocol/Kconfig @@ -0,0 +1,11 @@ +# +# vServices protocol drivers configuration +# + +if VSERVICES_SERVER || VSERVICES_CLIENT + +menu "Protocol drivers" + +endmenu + +endif # VSERVICES_SERVER || VSERVICES_CLIENT diff --git a/drivers/vservices/protocol/Makefile b/drivers/vservices/protocol/Makefile new file mode 100644 index 000000000000..a2162c814792 --- /dev/null +++ b/drivers/vservices/protocol/Makefile @@ -0,0 +1,3 @@ +# This is a autogenerated Makefile for vservice-linux-stacks + +obj-$(CONFIG_VSERVICES_SUPPORT) += core/ diff --git a/drivers/vservices/protocol/core/Makefile b/drivers/vservices/protocol/core/Makefile new file mode 100644 index 000000000000..6bef7f5f3cdd --- /dev/null +++ b/drivers/vservices/protocol/core/Makefile @@ -0,0 +1,7 @@ +ccflags-y += -Werror + +obj-$(CONFIG_VSERVICES_SERVER) += vservices_protocol_core_server.o +vservices_protocol_core_server-objs = server.o + +obj-$(CONFIG_VSERVICES_CLIENT) += vservices_protocol_core_client.o +vservices_protocol_core_client-objs = client.o diff --git a/drivers/vservices/protocol/core/client.c b/drivers/vservices/protocol/core/client.c new file mode 100644 index 000000000000..2dd213662fc2 --- /dev/null +++ b/drivers/vservices/protocol/core/client.c @@ -0,0 +1,1069 @@ + +/* + * 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. + */ + + /* + * This is the generated code for the core client protocol handling. + */ +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "../../transport.h" + +#define VS_MBUF_SIZE(mbuf) mbuf->size +#define VS_MBUF_DATA(mbuf) mbuf->data +#define VS_STATE_SERVICE_PTR(state) state->service + +/*** Linux driver model integration ***/ +struct vs_core_client_driver { + struct vs_client_core *client; + struct list_head list; + struct vs_service_driver vsdrv; +}; + +#define to_client_driver(d) \ + container_of(d, struct vs_core_client_driver, vsdrv) + +static void core_handle_start(struct vs_service_device *service) +{ + + struct vs_client_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_client_core *client __maybe_unused = + to_client_driver(vsdrv)->client; + + vs_service_state_lock(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (client->start) + client->start(state); + vs_service_state_unlock(service); +} + +static void core_handle_reset(struct vs_service_device *service) +{ + + struct vs_client_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_client_core *client __maybe_unused = + to_client_driver(vsdrv)->client; + + vs_service_state_lock(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (client->reset) + client->reset(state); + vs_service_state_unlock(service); +} + +static void core_handle_start_bh(struct vs_service_device *service) +{ + + struct vs_client_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_client_core *client __maybe_unused = + to_client_driver(vsdrv)->client; + + vs_service_state_lock_bh(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (client->start) + client->start(state); + vs_service_state_unlock_bh(service); +} + +static void core_handle_reset_bh(struct vs_service_device *service) +{ + + struct vs_client_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_client_core *client __maybe_unused = + to_client_driver(vsdrv)->client; + + vs_service_state_lock_bh(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (client->reset) + client->reset(state); + vs_service_state_unlock_bh(service); +} + +static int core_client_probe(struct vs_service_device *service); +static int core_client_remove(struct vs_service_device *service); +static int core_handle_message(struct vs_service_device *service, + struct vs_mbuf *_mbuf); +static void core_handle_notify(struct vs_service_device *service, + uint32_t flags); +static void core_handle_start(struct vs_service_device *service); +static void core_handle_start_bh(struct vs_service_device *service); +static void core_handle_reset(struct vs_service_device *service); +static void core_handle_reset_bh(struct vs_service_device *service); +static int core_handle_tx_ready(struct vs_service_device *service); + +int __vservice_core_client_register(struct vs_client_core *client, + const char *name, struct module *owner) +{ + int ret; + struct vs_core_client_driver *driver; + + if (client->tx_atomic && !client->rx_atomic) + return -EINVAL; + + driver = kzalloc(sizeof(*driver), GFP_KERNEL); + if (!driver) { + ret = -ENOMEM; + goto fail_alloc_driver; + } + + client->driver = &driver->vsdrv; + driver->client = client; + + driver->vsdrv.protocol = VSERVICE_CORE_PROTOCOL_NAME; + + driver->vsdrv.is_server = false; + driver->vsdrv.rx_atomic = client->rx_atomic; + driver->vsdrv.tx_atomic = client->tx_atomic; + + driver->vsdrv.probe = core_client_probe; + driver->vsdrv.remove = core_client_remove; + driver->vsdrv.receive = core_handle_message; + driver->vsdrv.notify = core_handle_notify; + driver->vsdrv.start = client->tx_atomic ? + core_handle_start_bh : core_handle_start; + driver->vsdrv.reset = client->tx_atomic ? + core_handle_reset_bh : core_handle_reset; + driver->vsdrv.tx_ready = core_handle_tx_ready; + driver->vsdrv.out_notify_count = 0; + driver->vsdrv.in_notify_count = 0; + driver->vsdrv.driver.name = name; + driver->vsdrv.driver.owner = owner; + driver->vsdrv.driver.bus = &vs_client_bus_type; + + ret = driver_register(&driver->vsdrv.driver); + + if (ret) { + goto fail_driver_register; + } + + return 0; + + fail_driver_register: + client->driver = NULL; + kfree(driver); + fail_alloc_driver: + return ret; +} + +EXPORT_SYMBOL(__vservice_core_client_register); + +int vservice_core_client_unregister(struct vs_client_core *client) +{ + struct vs_core_client_driver *driver; + + if (!client->driver) + return 0; + + driver = to_client_driver(client->driver); + driver_unregister(&driver->vsdrv.driver); + + client->driver = NULL; + kfree(driver); + + return 0; +} + +EXPORT_SYMBOL(vservice_core_client_unregister); + +static int core_client_probe(struct vs_service_device *service) +{ + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_client_core *client = to_client_driver(vsdrv)->client; + struct vs_client_core_state *state; + + state = client->alloc(service); + if (!state) + return -ENOMEM; + else if (IS_ERR(state)) + return PTR_ERR(state); + + state->service = vs_get_service(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + + dev_set_drvdata(&service->dev, state); + + return 0; +} + +static int core_client_remove(struct vs_service_device *service) +{ + struct vs_client_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_client_core *client = to_client_driver(vsdrv)->client; + + state->released = true; + dev_set_drvdata(&service->dev, NULL); + client->release(state); + + vs_put_service(service); + + return 0; +} + +static int core_handle_tx_ready(struct vs_service_device *service) +{ + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_client_core *client = to_client_driver(vsdrv)->client; + struct vs_client_core_state *state = dev_get_drvdata(&service->dev); + + if (client->tx_ready) + client->tx_ready(state); + + return 0; +} + +int vs_client_core_core_getbufs_service_created(struct vs_client_core_state + *_state, + struct vs_string *service_name, + struct vs_string *protocol_name, + struct vs_mbuf *_mbuf) +{ + const vs_message_id_t _msg_id = VSERVICE_CORE_CORE_MSG_SERVICE_CREATED; + const size_t _max_size = + sizeof(vs_message_id_t) + VSERVICE_CORE_SERVICE_NAME_SIZE + + VSERVICE_CORE_PROTOCOL_NAME_SIZE + 4UL; + const size_t _min_size = _max_size - VSERVICE_CORE_PROTOCOL_NAME_SIZE; + size_t _exact_size; + + if (*(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) != _msg_id) + return -EINVAL; + if ((VS_MBUF_SIZE(_mbuf) > _max_size) + || (VS_MBUF_SIZE(_mbuf) < _min_size)) + return -EBADMSG; + + service_name->ptr = + (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL); + service_name->max_size = VSERVICE_CORE_SERVICE_NAME_SIZE; + + protocol_name->ptr = + (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + + VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL); + protocol_name->max_size = + VS_MBUF_SIZE(_mbuf) - (sizeof(vs_message_id_t) + + VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL); + + /* Now check the size received is the exact size expected */ + _exact_size = + _max_size - (VSERVICE_CORE_PROTOCOL_NAME_SIZE - + protocol_name->max_size); + if (VS_MBUF_SIZE(_mbuf) != _exact_size) + return -EBADMSG; + + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_getbufs_service_created); +int vs_client_core_core_free_service_created(struct vs_client_core_state + *_state, + struct vs_string *service_name, + struct vs_string *protocol_name, + struct vs_mbuf *_mbuf) +{ + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_free_service_created); +int +vs_client_core_core_req_connect(struct vs_client_core_state *_state, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 0UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_client_core *_client = + to_client_driver(vsdrv)->client; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_REQ_CONNECT; + + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED__CONNECT; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT); + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_req_connect); +int +vs_client_core_core_req_disconnect(struct vs_client_core_state *_state, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 0UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_client_core *_client = + to_client_driver(vsdrv)->client; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_REQ_DISCONNECT; + + _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED__DISCONNECT; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT); + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_req_disconnect); +static int +core_core_handle_ack_connect(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 0UL; + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT, + VSERVICE_CORE_STATE_CONNECTED); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.ack_connect) + return _client->core.ack_connect(_state); + return 0; +} + +static int +core_core_handle_nack_connect(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT, + VSERVICE_CORE_STATE_DISCONNECTED); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.nack_connect) + return _client->core.nack_connect(_state); + return 0; +} + +EXPORT_SYMBOL(core_core_handle_ack_connect); +static int +core_core_handle_ack_disconnect(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 0UL; + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT, + VSERVICE_CORE_STATE_DISCONNECTED); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.ack_disconnect) + return _client->core.ack_disconnect(_state); + return 0; +} + +static int +core_core_handle_nack_disconnect(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT, + VSERVICE_CORE_STATE_CONNECTED); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.nack_disconnect) + return _client->core.nack_disconnect(_state); + return 0; +} + +EXPORT_SYMBOL(core_core_handle_ack_disconnect); +static int +vs_client_core_core_handle_startup(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 8UL; + uint32_t core_in_quota; + uint32_t core_out_quota; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_OFFLINE: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED; + + if (_client->core.state_change) + _client->core.state_change(_state, VSERVICE_CORE_STATE_OFFLINE, + VSERVICE_CORE_STATE_DISCONNECTED); + core_in_quota = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL); + core_out_quota = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.msg_startup) + return _client->core.msg_startup(_state, core_in_quota, + core_out_quota); + return 0; + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_handle_startup); +static int +vs_client_core_core_handle_shutdown(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 0UL; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED: + case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT: + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED: + _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED, + VSERVICE_CORE_STATE_OFFLINE); + break; + case VSERVICE_CORE_STATE_CONNECTED: + _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE; + + if (_client->core.state_change) + _client->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED, + VSERVICE_CORE_STATE_OFFLINE); + break; + + default: + break; + } + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.msg_shutdown) + return _client->core.msg_shutdown(_state); + return 0; + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_handle_shutdown); +static int +vs_client_core_core_handle_service_created(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _max_size = + sizeof(vs_message_id_t) + VSERVICE_CORE_SERVICE_NAME_SIZE + + VSERVICE_CORE_PROTOCOL_NAME_SIZE + 4UL; + uint32_t service_id; + struct vs_string service_name; + struct vs_string protocol_name; + const size_t _min_size = _max_size - VSERVICE_CORE_PROTOCOL_NAME_SIZE; + size_t _exact_size; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + /* The first check is to ensure the message isn't complete garbage */ + if ((VS_MBUF_SIZE(_mbuf) > _max_size) + || (VS_MBUF_SIZE(_mbuf) < _min_size)) + return -EBADMSG; + service_id = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL); + service_name.ptr = + (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL); + service_name.max_size = VSERVICE_CORE_SERVICE_NAME_SIZE; + + protocol_name.ptr = + (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + + VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL); + protocol_name.max_size = + VS_MBUF_SIZE(_mbuf) - (sizeof(vs_message_id_t) + + VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL); + + /* Now check the size received is the exact size expected */ + _exact_size = + _max_size - (VSERVICE_CORE_PROTOCOL_NAME_SIZE - + protocol_name.max_size); + if (VS_MBUF_SIZE(_mbuf) != _exact_size) + return -EBADMSG; + if (_client->core.msg_service_created) + return _client->core.msg_service_created(_state, service_id, + service_name, + protocol_name, _mbuf); + return 0; + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_handle_service_created); +static int +vs_client_core_core_handle_service_removed(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 4UL; + uint32_t service_id; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + service_id = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.msg_service_removed) + return _client->core.msg_service_removed(_state, service_id); + return 0; + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_handle_service_removed); +static int +vs_client_core_core_handle_server_ready(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 28UL; + uint32_t service_id; + uint32_t in_quota; + uint32_t out_quota; + uint32_t in_bit_offset; + uint32_t in_num_bits; + uint32_t out_bit_offset; + uint32_t out_num_bits; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + service_id = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL); + in_quota = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL); + out_quota = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 8UL); + in_bit_offset = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + + 12UL); + in_num_bits = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + + 16UL); + out_bit_offset = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + + 20UL); + out_num_bits = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + + 24UL); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.msg_server_ready) + return _client->core.msg_server_ready(_state, service_id, + in_quota, out_quota, + in_bit_offset, + in_num_bits, + out_bit_offset, + out_num_bits); + return 0; + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_handle_server_ready); +static int +vs_client_core_core_handle_service_reset(const struct vs_client_core *_client, + struct vs_client_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 4UL; + uint32_t service_id; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + service_id = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_client->core.msg_service_reset) + return _client->core.msg_service_reset(_state, service_id); + return 0; + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_handle_service_reset); +int +vs_client_core_core_send_service_reset(struct vs_client_core_state *_state, + uint32_t service_id, gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 4UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_client_core *_client = + to_client_driver(vsdrv)->client; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_MSG_SERVICE_RESET; + + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) = + service_id; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + return 0; +} + +EXPORT_SYMBOL(vs_client_core_core_send_service_reset); +static int +core_handle_message(struct vs_service_device *service, struct vs_mbuf *_mbuf) +{ + vs_message_id_t message_id; + __maybe_unused struct vs_client_core_state *state = + dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + __maybe_unused struct vs_client_core *client = + to_client_driver(vsdrv)->client; + + int ret; + + /* Extract the message ID */ + if (VS_MBUF_SIZE(_mbuf) < sizeof(message_id)) { + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Invalid message size %zd\n", + __func__, __LINE__, VS_MBUF_SIZE(_mbuf)); + + return -EBADMSG; + } + + message_id = *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)); + + switch (message_id) { + +/** interface core **/ +/* command in sync connect */ + case VSERVICE_CORE_CORE_ACK_CONNECT: + ret = core_core_handle_ack_connect(client, state, _mbuf); + break; + case VSERVICE_CORE_CORE_NACK_CONNECT: + ret = core_core_handle_nack_connect(client, state, _mbuf); + break; + +/* command in sync disconnect */ + case VSERVICE_CORE_CORE_ACK_DISCONNECT: + ret = core_core_handle_ack_disconnect(client, state, _mbuf); + break; + case VSERVICE_CORE_CORE_NACK_DISCONNECT: + ret = core_core_handle_nack_disconnect(client, state, _mbuf); + break; + +/* message startup */ + case VSERVICE_CORE_CORE_MSG_STARTUP: + ret = vs_client_core_core_handle_startup(client, state, _mbuf); + break; + +/* message shutdown */ + case VSERVICE_CORE_CORE_MSG_SHUTDOWN: + ret = vs_client_core_core_handle_shutdown(client, state, _mbuf); + break; + +/* message service_created */ + case VSERVICE_CORE_CORE_MSG_SERVICE_CREATED: + ret = + vs_client_core_core_handle_service_created(client, state, + _mbuf); + break; + +/* message service_removed */ + case VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED: + ret = + vs_client_core_core_handle_service_removed(client, state, + _mbuf); + break; + +/* message server_ready */ + case VSERVICE_CORE_CORE_MSG_SERVER_READY: + ret = + vs_client_core_core_handle_server_ready(client, state, + _mbuf); + break; + +/* message service_reset */ + case VSERVICE_CORE_CORE_MSG_SERVICE_RESET: + ret = + vs_client_core_core_handle_service_reset(client, state, + _mbuf); + break; + + default: + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Unknown message type %d\n", + __func__, __LINE__, (int)message_id); + + ret = -EPROTO; + break; + } + + if (ret) { + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Handler for message type %d returned %d\n", + __func__, __LINE__, (int)message_id, ret); + + } + + return ret; +} + +static void core_handle_notify(struct vs_service_device *service, + uint32_t notify_bits) +{ + __maybe_unused struct vs_client_core_state *state = + dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + __maybe_unused struct vs_client_core *client = + to_client_driver(vsdrv)->client; + + uint32_t bits = notify_bits; + int ret; + + while (bits) { + uint32_t not = __ffs(bits); + switch (not) { + + /** interface core **/ + + default: + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Unknown notification %d\n", + __func__, __LINE__, (int)not); + + ret = -EPROTO; + break; + + } + bits &= ~(1 << not); + if (ret) { + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Handler for notification %d returned %d\n", + __func__, __LINE__, (int)not, ret); + + } + } +} + +MODULE_DESCRIPTION("OKL4 Virtual Services coreClient Protocol Driver"); +MODULE_AUTHOR("Open Kernel Labs, Inc"); diff --git a/drivers/vservices/protocol/core/server.c b/drivers/vservices/protocol/core/server.c new file mode 100644 index 000000000000..c3f36866a7f7 --- /dev/null +++ b/drivers/vservices/protocol/core/server.c @@ -0,0 +1,1226 @@ + +/* + * 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. + */ + + /* + * This is the generated code for the core server protocol handling. + */ +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "../../transport.h" + +#define VS_MBUF_SIZE(mbuf) mbuf->size +#define VS_MBUF_DATA(mbuf) mbuf->data +#define VS_STATE_SERVICE_PTR(state) state->service + +/*** Linux driver model integration ***/ +struct vs_core_server_driver { + struct vs_server_core *server; + struct list_head list; + struct vs_service_driver vsdrv; +}; + +#define to_server_driver(d) \ + container_of(d, struct vs_core_server_driver, vsdrv) + +static void core_handle_start(struct vs_service_device *service) +{ + + struct vs_server_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_server_core *server __maybe_unused = + to_server_driver(vsdrv)->server; + + vs_service_state_lock(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (server->start) + server->start(state); + vs_service_state_unlock(service); +} + +static void core_handle_reset(struct vs_service_device *service) +{ + + struct vs_server_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_server_core *server __maybe_unused = + to_server_driver(vsdrv)->server; + + vs_service_state_lock(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (server->reset) + server->reset(state); + vs_service_state_unlock(service); +} + +static void core_handle_start_bh(struct vs_service_device *service) +{ + + struct vs_server_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_server_core *server __maybe_unused = + to_server_driver(vsdrv)->server; + + vs_service_state_lock_bh(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (server->start) + server->start(state); + vs_service_state_unlock_bh(service); +} + +static void core_handle_reset_bh(struct vs_service_device *service) +{ + + struct vs_server_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_server_core *server __maybe_unused = + to_server_driver(vsdrv)->server; + + vs_service_state_lock_bh(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + if (server->reset) + server->reset(state); + vs_service_state_unlock_bh(service); +} + +static int core_server_probe(struct vs_service_device *service); +static int core_server_remove(struct vs_service_device *service); +static int core_handle_message(struct vs_service_device *service, + struct vs_mbuf *_mbuf); +static void core_handle_notify(struct vs_service_device *service, + uint32_t flags); +static void core_handle_start(struct vs_service_device *service); +static void core_handle_start_bh(struct vs_service_device *service); +static void core_handle_reset(struct vs_service_device *service); +static void core_handle_reset_bh(struct vs_service_device *service); +static int core_handle_tx_ready(struct vs_service_device *service); + +int __vservice_core_server_register(struct vs_server_core *server, + const char *name, struct module *owner) +{ + int ret; + struct vs_core_server_driver *driver; + + if (server->tx_atomic && !server->rx_atomic) + return -EINVAL; + + driver = kzalloc(sizeof(*driver), GFP_KERNEL); + if (!driver) { + ret = -ENOMEM; + goto fail_alloc_driver; + } + + server->driver = &driver->vsdrv; + driver->server = server; + + driver->vsdrv.protocol = VSERVICE_CORE_PROTOCOL_NAME; + + driver->vsdrv.is_server = true; + driver->vsdrv.rx_atomic = server->rx_atomic; + driver->vsdrv.tx_atomic = server->tx_atomic; + /* FIXME Jira ticket SDK-2835 - philipd. */ + driver->vsdrv.in_quota_min = 1; + driver->vsdrv.in_quota_best = server->in_quota_best ? + server->in_quota_best : driver->vsdrv.in_quota_min; + /* FIXME Jira ticket SDK-2835 - philipd. */ + driver->vsdrv.out_quota_min = 1; + driver->vsdrv.out_quota_best = server->out_quota_best ? + server->out_quota_best : driver->vsdrv.out_quota_min; + driver->vsdrv.in_notify_count = VSERVICE_CORE_NBIT_IN__COUNT; + driver->vsdrv.out_notify_count = VSERVICE_CORE_NBIT_OUT__COUNT; + + driver->vsdrv.probe = core_server_probe; + driver->vsdrv.remove = core_server_remove; + driver->vsdrv.receive = core_handle_message; + driver->vsdrv.notify = core_handle_notify; + driver->vsdrv.start = server->tx_atomic ? + core_handle_start_bh : core_handle_start; + driver->vsdrv.reset = server->tx_atomic ? + core_handle_reset_bh : core_handle_reset; + driver->vsdrv.tx_ready = core_handle_tx_ready; + driver->vsdrv.out_notify_count = 0; + driver->vsdrv.in_notify_count = 0; + driver->vsdrv.driver.name = name; + driver->vsdrv.driver.owner = owner; + driver->vsdrv.driver.bus = &vs_server_bus_type; + + ret = driver_register(&driver->vsdrv.driver); + + if (ret) { + goto fail_driver_register; + } + + return 0; + + fail_driver_register: + server->driver = NULL; + kfree(driver); + fail_alloc_driver: + return ret; +} + +EXPORT_SYMBOL(__vservice_core_server_register); + +int vservice_core_server_unregister(struct vs_server_core *server) +{ + struct vs_core_server_driver *driver; + + if (!server->driver) + return 0; + + driver = to_server_driver(server->driver); + driver_unregister(&driver->vsdrv.driver); + + server->driver = NULL; + kfree(driver); + + return 0; +} + +EXPORT_SYMBOL(vservice_core_server_unregister); + +static int core_server_probe(struct vs_service_device *service) +{ + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_server_core *server = to_server_driver(vsdrv)->server; + struct vs_server_core_state *state; + + state = server->alloc(service); + if (!state) + return -ENOMEM; + else if (IS_ERR(state)) + return PTR_ERR(state); + + state->service = vs_get_service(service); + state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE; + + dev_set_drvdata(&service->dev, state); + + return 0; +} + +static int core_server_remove(struct vs_service_device *service) +{ + struct vs_server_core_state *state = dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_server_core *server = to_server_driver(vsdrv)->server; + + state->released = true; + dev_set_drvdata(&service->dev, NULL); + server->release(state); + + vs_put_service(service); + + return 0; +} + +static int core_handle_tx_ready(struct vs_service_device *service) +{ + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + struct vs_server_core *server = to_server_driver(vsdrv)->server; + struct vs_server_core_state *state = dev_get_drvdata(&service->dev); + + if (server->tx_ready) + server->tx_ready(state); + + return 0; +} + +struct vs_mbuf *vs_server_core_core_alloc_service_created(struct + vs_server_core_state + *_state, + struct vs_string + *service_name, + struct vs_string + *protocol_name, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + const vs_message_id_t _msg_id = VSERVICE_CORE_CORE_MSG_SERVICE_CREATED; + const uint32_t _msg_size = + sizeof(vs_message_id_t) + VSERVICE_CORE_SERVICE_NAME_SIZE + + VSERVICE_CORE_PROTOCOL_NAME_SIZE + 4UL; + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return _mbuf; + if (!_mbuf) { + + WARN_ON_ONCE(1); + return ERR_PTR(-ENOMEM); + } + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = _msg_id; + + if (!service_name) + goto fail; + service_name->ptr = + (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL); + service_name->max_size = VSERVICE_CORE_SERVICE_NAME_SIZE; + if (!protocol_name) + goto fail; + protocol_name->ptr = + (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + + VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL); + protocol_name->max_size = VSERVICE_CORE_PROTOCOL_NAME_SIZE; + + return _mbuf; + + fail: + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + return NULL; +} + +EXPORT_SYMBOL(vs_server_core_core_alloc_service_created); +int vs_server_core_core_free_service_created(struct vs_server_core_state + *_state, + struct vs_string *service_name, + struct vs_string *protocol_name, + struct vs_mbuf *_mbuf) +{ + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_free_service_created); +int +vs_server_core_core_send_ack_connect(struct vs_server_core_state *_state, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 0UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_ACK_CONNECT; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT, + VSERVICE_CORE_STATE_CONNECTED); + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_ack_connect); +int +vs_server_core_core_send_nack_connect(struct vs_server_core_state *_state, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 0UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_NACK_CONNECT; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT, + VSERVICE_CORE_STATE_DISCONNECTED); + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_nack_connect); +int +vs_server_core_core_send_ack_disconnect(struct vs_server_core_state *_state, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 0UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_ACK_DISCONNECT; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT, + VSERVICE_CORE_STATE_DISCONNECTED); + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_ack_disconnect); +int +vs_server_core_core_send_nack_disconnect(struct vs_server_core_state *_state, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 0UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_NACK_DISCONNECT; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT, + VSERVICE_CORE_STATE_CONNECTED); + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_nack_disconnect); +static int +vs_server_core_core_handle_req_connect(const struct vs_server_core *_server, + struct vs_server_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 0UL; + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED__CONNECT; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_server->core.req_connect) + return _server->core.req_connect(_state); + else + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: No handler registered for _server->core.req_connect, command will never be acknowledged\n", + __func__, __LINE__); + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_handle_req_connect); +static int +vs_server_core_core_handle_req_disconnect(const struct vs_server_core *_server, + struct vs_server_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 0UL; + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED__DISCONNECT; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_server->core.req_disconnect) + return _server->core.req_disconnect(_state); + else + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: No handler registered for _server->core.req_disconnect, command will never be acknowledged\n", + __func__, __LINE__); + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_handle_req_disconnect); +int +vs_server_core_core_send_startup(struct vs_server_core_state *_state, + uint32_t core_in_quota, + uint32_t core_out_quota, gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 8UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_OFFLINE: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_MSG_STARTUP; + + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) = + core_in_quota; + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL) = + core_out_quota; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED; + + if (_server->core.state_change) + _server->core.state_change(_state, VSERVICE_CORE_STATE_OFFLINE, + VSERVICE_CORE_STATE_DISCONNECTED); + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_startup); +int +vs_server_core_core_send_shutdown(struct vs_server_core_state *_state, + gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 0UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED: + case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT: + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_MSG_SHUTDOWN; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_DISCONNECTED: + _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_DISCONNECTED, + VSERVICE_CORE_STATE_OFFLINE); + break; + case VSERVICE_CORE_STATE_CONNECTED: + _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE; + + if (_server->core.state_change) + _server->core.state_change(_state, + VSERVICE_CORE_STATE_CONNECTED, + VSERVICE_CORE_STATE_OFFLINE); + break; + + default: + break; + } + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_shutdown); +int +vs_server_core_core_send_service_created(struct vs_server_core_state *_state, + uint32_t service_id, + struct vs_string service_name, + struct vs_string protocol_name, + struct vs_mbuf *_mbuf) +{ + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + if (*(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) != + VSERVICE_CORE_CORE_MSG_SERVICE_CREATED) + + return -EINVAL; + + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) = + service_id; + { + size_t _size = strnlen(service_name.ptr, service_name.max_size); + if ((_size + sizeof(vs_message_id_t) + 4UL) > + VS_MBUF_SIZE(_mbuf)) + return -EINVAL; + + memset(service_name.ptr + _size, 0, + service_name.max_size - _size); + } + { + size_t _size = + strnlen(protocol_name.ptr, protocol_name.max_size); + if ((_size + sizeof(vs_message_id_t) + + VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL) > + VS_MBUF_SIZE(_mbuf)) + return -EINVAL; + + if (_size < protocol_name.max_size) + VS_MBUF_SIZE(_mbuf) -= (protocol_name.max_size - _size); + + } + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_service_created); +int +vs_server_core_core_send_service_removed(struct vs_server_core_state *_state, + uint32_t service_id, gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 4UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED; + + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) = + service_id; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_service_removed); +int +vs_server_core_core_send_server_ready(struct vs_server_core_state *_state, + uint32_t service_id, uint32_t in_quota, + uint32_t out_quota, + uint32_t in_bit_offset, + uint32_t in_num_bits, + uint32_t out_bit_offset, + uint32_t out_num_bits, gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 28UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_MSG_SERVER_READY; + + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) = + service_id; + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL) = + in_quota; + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 8UL) = + out_quota; + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 12UL) = + in_bit_offset; + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 16UL) = + in_num_bits; + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 20UL) = + out_bit_offset; + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 24UL) = + out_num_bits; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_server_ready); +int +vs_server_core_core_send_service_reset(struct vs_server_core_state *_state, + uint32_t service_id, gfp_t flags) +{ + struct vs_mbuf *_mbuf; + + const size_t _msg_size = sizeof(vs_message_id_t) + 4UL; + + struct vs_service_driver *vsdrv = + to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver); + __maybe_unused struct vs_server_core *_server = + to_server_driver(vsdrv)->server; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + _mbuf = + vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size, + flags); + if (IS_ERR(_mbuf)) + return PTR_ERR(_mbuf); + if (!_mbuf) { + + WARN_ON_ONCE(1); + + return -ENOMEM; + } + + *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = + VSERVICE_CORE_CORE_MSG_SERVICE_RESET; + + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) = + service_id; + + { + int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (err) { + dev_warn(&_state->service->dev, + "[%s:%d] Protocol warning: Error %d sending message on transport.\n", + __func__, __LINE__, err); + + return err; + } + } + + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_send_service_reset); +static int +vs_server_core_core_handle_service_reset(const struct vs_server_core *_server, + struct vs_server_core_state *_state, + struct vs_mbuf *_mbuf) +{ + const size_t _expected_size = sizeof(vs_message_id_t) + 4UL; + uint32_t service_id; + + switch (_state->state.core.statenum) { + case VSERVICE_CORE_STATE_CONNECTED: + + break; + + default: + dev_err(&_state->service->dev, + "[%s:%d] Protocol error: In wrong protocol state %d - %s\n", + __func__, __LINE__, _state->state.core.statenum, + vservice_core_get_state_string(_state->state.core)); + + return -EPROTO; + + } + + if (VS_MBUF_SIZE(_mbuf) < _expected_size) + return -EBADMSG; + + service_id = + *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL); + vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf); + if (_server->core.msg_service_reset) + return _server->core.msg_service_reset(_state, service_id); + return 0; + return 0; +} + +EXPORT_SYMBOL(vs_server_core_core_handle_service_reset); +static int +core_handle_message(struct vs_service_device *service, struct vs_mbuf *_mbuf) +{ + vs_message_id_t message_id; + __maybe_unused struct vs_server_core_state *state = + dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + __maybe_unused struct vs_server_core *server = + to_server_driver(vsdrv)->server; + + int ret; + + /* Extract the message ID */ + if (VS_MBUF_SIZE(_mbuf) < sizeof(message_id)) { + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Invalid message size %zd\n", + __func__, __LINE__, VS_MBUF_SIZE(_mbuf)); + + return -EBADMSG; + } + + message_id = *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)); + + switch (message_id) { + +/** interface core **/ +/* command in sync connect */ + case VSERVICE_CORE_CORE_REQ_CONNECT: + ret = + vs_server_core_core_handle_req_connect(server, state, + _mbuf); + break; + +/* command in sync disconnect */ + case VSERVICE_CORE_CORE_REQ_DISCONNECT: + ret = + vs_server_core_core_handle_req_disconnect(server, state, + _mbuf); + break; + +/* message service_reset */ + case VSERVICE_CORE_CORE_MSG_SERVICE_RESET: + ret = + vs_server_core_core_handle_service_reset(server, state, + _mbuf); + break; + + default: + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Unknown message type %d\n", + __func__, __LINE__, (int)message_id); + + ret = -EPROTO; + break; + } + + if (ret) { + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Handler for message type %d returned %d\n", + __func__, __LINE__, (int)message_id, ret); + + } + + return ret; +} + +static void core_handle_notify(struct vs_service_device *service, + uint32_t notify_bits) +{ + __maybe_unused struct vs_server_core_state *state = + dev_get_drvdata(&service->dev); + struct vs_service_driver *vsdrv = + to_vs_service_driver(service->dev.driver); + __maybe_unused struct vs_server_core *server = + to_server_driver(vsdrv)->server; + + uint32_t bits = notify_bits; + int ret; + + while (bits) { + uint32_t not = __ffs(bits); + switch (not) { + + /** interface core **/ + + default: + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Unknown notification %d\n", + __func__, __LINE__, (int)not); + + ret = -EPROTO; + break; + + } + bits &= ~(1 << not); + if (ret) { + dev_err(&state->service->dev, + "[%s:%d] Protocol error: Handler for notification %d returned %d\n", + __func__, __LINE__, (int)not, ret); + + } + } +} + +MODULE_DESCRIPTION("OKL4 Virtual Services coreServer Protocol Driver"); +MODULE_AUTHOR("Open Kernel Labs, Inc"); diff --git a/drivers/vservices/session.c b/drivers/vservices/session.c new file mode 100644 index 000000000000..88e8933dfc50 --- /dev/null +++ b/drivers/vservices/session.c @@ -0,0 +1,2911 @@ +/* + * drivers/vservices/session.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. + * + * This is the generic session-management code for the vServices framework. + * It creates service and session devices on request from session and + * transport drivers, respectively; it also queues incoming messages from the + * transport and distributes them to the session's services. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "session.h" +#include "transport.h" +#include "compat.h" + +/* Minimum required time between resets to avoid throttling */ +#define RESET_THROTTLE_TIME msecs_to_jiffies(1000) + +/* + * Minimum/maximum reset throttling time. The reset throttle will start at + * the minimum and increase to the maximum exponetially. + */ +#define RESET_THROTTLE_MIN RESET_THROTTLE_TIME +#define RESET_THROTTLE_MAX msecs_to_jiffies(8 * 1000) + +/* + * If the reset is being throttled and a sane reset (doesn't need throttling) + * is requested, then if the service's reset delay mutliplied by this value + * has elapsed throttling is disabled. + */ +#define RESET_THROTTLE_COOL_OFF_MULT 2 + +/* IDR of session ids to sessions */ +static DEFINE_IDR(session_idr); +DEFINE_MUTEX(vs_session_lock); +EXPORT_SYMBOL_GPL(vs_session_lock); + +/* Notifier list for vService session events */ +static BLOCKING_NOTIFIER_HEAD(vs_session_notifier_list); + +static unsigned long default_debug_mask; +module_param(default_debug_mask, ulong, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(default_debug_mask, "Default vServices debug mask"); + +/* vServices root in sysfs at /sys/vservices */ +struct kobject *vservices_root; +EXPORT_SYMBOL_GPL(vservices_root); + +/* vServices server root in sysfs at /sys/vservices/server-sessions */ +struct kobject *vservices_server_root; +EXPORT_SYMBOL_GPL(vservices_server_root); + +/* vServices client root in sysfs at /sys/vservices/client-sessions */ +struct kobject *vservices_client_root; +EXPORT_SYMBOL_GPL(vservices_client_root); + +#ifdef CONFIG_VSERVICES_CHAR_DEV +struct vs_service_device *vs_service_lookup_by_devt(dev_t dev) +{ + struct vs_session_device *session; + struct vs_service_device *service; + + mutex_lock(&vs_session_lock); + session = idr_find(&session_idr, MINOR(dev) / VS_MAX_SERVICES); + get_device(&session->dev); + mutex_unlock(&vs_session_lock); + + service = vs_session_get_service(session, + MINOR(dev) % VS_MAX_SERVICES); + put_device(&session->dev); + + return service; +} +#endif + +struct vs_session_for_each_data { + int (*fn)(struct vs_session_device *session, void *data); + void *data; +}; + +int vs_session_for_each_from_idr(int id, void *session, void *_data) +{ + struct vs_session_for_each_data *data = + (struct vs_session_for_each_data *)_data; + return data->fn(session, data->data); +} + +/** + * vs_session_for_each_locked - call a callback function for each session + * @fn: function to call + * @data: opaque pointer that is passed through to the function + */ +extern int vs_session_for_each_locked( + int (*fn)(struct vs_session_device *session, void *data), + void *data) +{ + struct vs_session_for_each_data priv = { .fn = fn, .data = data }; + + lockdep_assert_held(&vs_session_lock); + + return idr_for_each(&session_idr, vs_session_for_each_from_idr, + &priv); +} +EXPORT_SYMBOL(vs_session_for_each_locked); + +/** + * vs_register_notify - register a notifier callback for vServices events + * @nb: pointer to the notifier block for the callback events. + */ +void vs_session_register_notify(struct notifier_block *nb) +{ + blocking_notifier_chain_register(&vs_session_notifier_list, nb); +} +EXPORT_SYMBOL(vs_session_register_notify); + +/** + * vs_unregister_notify - unregister a notifier callback for vServices events + * @nb: pointer to the notifier block for the callback events. + */ +void vs_session_unregister_notify(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&vs_session_notifier_list, nb); +} +EXPORT_SYMBOL(vs_session_unregister_notify); + +/* + * Helper function for returning how long ago something happened + * Marked as __maybe_unused since this is only needed when + * CONFIG_VSERVICES_DEBUG is enabled, but cannot be removed because it + * will cause compile time errors. + */ +static __maybe_unused unsigned msecs_ago(unsigned long jiffy_value) +{ + return jiffies_to_msecs(jiffies - jiffy_value); +} + +static void session_fatal_error_work(struct work_struct *work) +{ + struct vs_session_device *session = container_of(work, + struct vs_session_device, fatal_error_work); + + session->transport->vt->reset(session->transport); +} + +static void session_fatal_error(struct vs_session_device *session, gfp_t gfp) +{ + schedule_work(&session->fatal_error_work); +} + +/* + * Service readiness state machine + * + * The states are: + * + * INIT: Initial state. Service may not be completely configured yet + * (typically because the protocol hasn't been set); call vs_service_start + * once configuration is complete. The disable count must be nonzero, and + * must never reach zero in this state. + * DISABLED: Service is not permitted to communicate. Non-core services are + * in this state whenever the core protocol and/or transport state does not + * allow them to be active; core services are only in this state transiently. + * The disable count must be nonzero; when it reaches zero, the service + * transitions to RESET state. + * RESET: Service drivers are inactive at both ends, but the core service + * state allows the service to become active. The session will schedule a + * future transition to READY state when entering this state, but the + * transition may be delayed to throttle the rate at which resets occur. + * READY: All core-service and session-layer policy allows the service to + * communicate; it will become active as soon as it has a protocol driver. + * ACTIVE: The driver is present and communicating. + * LOCAL_RESET: We have initiated a reset at this end, but the remote end has + * not yet acknowledged it. We will enter the RESET state on receiving + * acknowledgement, unless the disable count is nonzero in which case we + * will enter DISABLED state. + * LOCAL_DELETE: As for LOCAL_RESET, but we will enter the DELETED state + * instead of RESET or DISABLED. + * DELETED: The service is no longer present on the session; the service + * device structure may still exist because something is holding a reference + * to it. + * + * The permitted transitions are: + * + * From To Trigger + * INIT DISABLED vs_service_start + * DISABLED RESET vs_service_enable (disable_count -> 0) + * RESET READY End of throttle delay (may be 0) + * READY ACTIVE Latter of probe() and entering READY + * {READY, ACTIVE} + * LOCAL_RESET vs_service_reset + * {READY, ACTIVE, LOCAL_RESET} + * RESET vs_service_handle_reset (server) + * RESET DISABLED vs_service_disable (server) + * {READY, ACTIVE, LOCAL_RESET} + * DISABLED vs_service_handle_reset (client) + * {INIT, RESET, READY, ACTIVE, LOCAL_RESET} + * DISABLED vs_service_disable_noncore + * {ACTIVE, LOCAL_RESET} + * LOCAL_DELETE vs_service_delete + * {INIT, DISABLED, RESET, READY} + * DELETED vs_service_delete + * LOCAL_DELETE DELETED vs_service_handle_reset + * vs_service_disable_noncore + * + * See the documentation for the triggers for details. + */ + +enum vs_service_readiness { + VS_SERVICE_INIT, + VS_SERVICE_DISABLED, + VS_SERVICE_RESET, + VS_SERVICE_READY, + VS_SERVICE_ACTIVE, + VS_SERVICE_LOCAL_RESET, + VS_SERVICE_LOCAL_DELETE, + VS_SERVICE_DELETED, +}; + +/* Session activation states. */ +enum { + VS_SESSION_RESET, + VS_SESSION_ACTIVATE, + VS_SESSION_ACTIVE, +}; + +/** + * vs_service_start - Start a service by moving it from the init state to the + * disabled state. + * + * @service: The service to start. + * + * Returns true if the service was started, or false if it was not. + */ +bool vs_service_start(struct vs_service_device *service) +{ + struct vs_session_device *session = vs_service_get_session(service); + struct vs_session_driver *session_drv = + to_vs_session_driver(session->dev.driver); + + WARN_ON(!service->protocol); + + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + if (service->readiness != VS_SERVICE_INIT) { + if (service->readiness != VS_SERVICE_DELETED) + dev_err(&service->dev, + "start called from invalid state %d\n", + service->readiness); + mutex_unlock(&service->ready_lock); + return false; + } + + if (service->id != 0 && session_drv->service_added) { + int err = session_drv->service_added(session, service); + if (err < 0) { + dev_err(&session->dev, "Failed to add service %d: %d\n", + service->id, err); + mutex_unlock(&service->ready_lock); + return false; + } + } + + service->readiness = VS_SERVICE_DISABLED; + service->disable_count = 1; + service->last_reset_request = jiffies; + + mutex_unlock(&service->ready_lock); + + /* Tell userspace about the service. */ + dev_set_uevent_suppress(&service->dev, false); + kobject_uevent(&service->dev.kobj, KOBJ_ADD); + + return true; +} +EXPORT_SYMBOL_GPL(vs_service_start); + +static void cancel_pending_rx(struct vs_service_device *service); +static void queue_ready_work(struct vs_service_device *service); + +static void __try_start_service(struct vs_service_device *service) +{ + struct vs_session_device *session = vs_service_get_session(service); + struct vs_session_driver *session_drv = + to_vs_session_driver(session->dev.driver); + struct vs_transport *transport; + int err; + struct vs_service_driver *driver; + + lockdep_assert_held(&service->ready_lock); + + /* We can't start if the service is not ready yet. */ + if (service->readiness != VS_SERVICE_READY) + return; + + /* + * There should never be anything in the RX queue at this point. + * If there is, it can seriously confuse the service drivers for + * no obvious reason, so we check. + */ + if (WARN_ON(!list_empty(&service->rx_queue))) + cancel_pending_rx(service); + + if (!service->driver_probed) { + vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, + "ready with no driver\n"); + return; + } + + /* Prepare the transport to support the service. */ + transport = session->transport; + err = transport->vt->service_start(transport, service); + + if (err < 0) { + /* fatal error attempting to start; reset and try again */ + service->readiness = VS_SERVICE_RESET; + service->last_reset_request = jiffies; + service->last_reset = jiffies; + queue_ready_work(service); + + return; + } + + service->readiness = VS_SERVICE_ACTIVE; + + driver = to_vs_service_driver(service->dev.driver); + if (driver->start) + driver->start(service); + + if (service->id && session_drv->service_start) { + err = session_drv->service_start(session, service); + if (err < 0) { + dev_err(&session->dev, "Failed to start service %s (%d): %d\n", + dev_name(&service->dev), + service->id, err); + session_fatal_error(session, GFP_KERNEL); + } + } +} + +static void try_start_service(struct vs_service_device *service) +{ + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + __try_start_service(service); + + mutex_unlock(&service->ready_lock); +} + +static void service_ready_work(struct work_struct *work) +{ + struct vs_service_device *service = container_of(work, + struct vs_service_device, ready_work.work); + struct vs_session_device *session = vs_service_get_session(service); + + vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, + "ready work - last reset request was %u ms ago\n", + msecs_ago(service->last_reset_request)); + + /* + * Make sure there's no reset work pending from an earlier driver + * failure. We should already be inactive at this point, so it's safe + * to just cancel it. + */ + cancel_work_sync(&service->reset_work); + + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + if (service->readiness != VS_SERVICE_RESET) { + vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, + "ready work found readiness of %d, doing nothing\n", + service->readiness); + mutex_unlock(&service->ready_lock); + return; + } + + service->readiness = VS_SERVICE_READY; + /* Record the time at which this happened, for throttling. */ + service->last_ready = jiffies; + + /* Tell userspace that the service is ready. */ + kobject_uevent(&service->dev.kobj, KOBJ_ONLINE); + + /* Start the service, if it has a driver attached. */ + __try_start_service(service); + + mutex_unlock(&service->ready_lock); +} + +static int __enable_service(struct vs_service_device *service); + +/** + * __reset_service - make a service inactive, and tell its driver, the + * transport, and possibly the remote partner + * @service: The service to reset + * @notify_remote: If true, the partner is notified of the reset + * + * This routine is called to make an active service inactive. If the given + * service is currently active, it drops any queued messages for the service, + * and then informs the service driver and the transport layer that the + * service has reset. It sets the service readiness to VS_SERVICE_LOCAL_RESET + * to indicate that the driver is no longer active. + * + * This routine has no effect on services that are not active. + * + * The caller must hold the target service's ready lock. + */ +static void __reset_service(struct vs_service_device *service, + bool notify_remote) +{ + struct vs_session_device *session = vs_service_get_session(service); + struct vs_session_driver *session_drv = + to_vs_session_driver(session->dev.driver); + struct vs_service_driver *driver = NULL; + struct vs_transport *transport; + int err; + + lockdep_assert_held(&service->ready_lock); + + /* If we're already inactive, there's nothing to do. */ + if (service->readiness != VS_SERVICE_ACTIVE) + return; + + service->last_reset = jiffies; + service->readiness = VS_SERVICE_LOCAL_RESET; + + cancel_pending_rx(service); + + if (!WARN_ON(!service->driver_probed)) + driver = to_vs_service_driver(service->dev.driver); + + if (driver && driver->reset) + driver->reset(service); + + wake_up_all(&service->quota_wq); + + transport = vs_service_get_session(service)->transport; + + /* + * Ask the transport to reset the service. If this returns a positive + * value, we need to leave the service disabled, and the transport + * will re-enable it. To avoid allowing the disable count to go + * negative if that re-enable races with this callback returning, we + * disable the service beforehand and re-enable it if the callback + * returns zero. + */ + service->disable_count++; + err = transport->vt->service_reset(transport, service); + if (err < 0) { + dev_err(&session->dev, "Failed to reset service %d: %d (transport)\n", + service->id, err); + session_fatal_error(session, GFP_KERNEL); + } else if (!err) { + err = __enable_service(service); + } + + if (notify_remote) { + if (service->id) { + err = session_drv->service_local_reset(session, + service); + if (err == VS_SERVICE_ALREADY_RESET) { + service->readiness = VS_SERVICE_RESET; + service->last_reset = jiffies; + queue_ready_work(service); + + } else if (err < 0) { + dev_err(&session->dev, "Failed to reset service %d: %d (session)\n", + service->id, err); + session_fatal_error(session, GFP_KERNEL); + } + } else { + session->transport->vt->reset(session->transport); + } + } + + /* Tell userspace that the service is no longer active. */ + kobject_uevent(&service->dev.kobj, KOBJ_OFFLINE); +} + +/** + * reset_service - reset a service and inform the remote partner + * @service: The service to reset + * + * This routine is called when a reset is locally initiated (other than + * implicitly by a session / core service reset). It bumps the reset request + * timestamp, acquires the necessary locks, and calls __reset_service. + * + * This routine returns with the service ready lock held, to allow the caller + * to make any other state changes that must be atomic with the service + * reset. + */ +static void reset_service(struct vs_service_device *service) + __acquires(service->ready_lock) +{ + service->last_reset_request = jiffies; + + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + __reset_service(service, true); +} + +/** + * vs_service_reset - initiate a service reset + * @service: the service that is to be reset + * @caller: the service that is initiating the reset + * + * This routine informs the partner that the given service is being reset, + * then disables and flushes the service's receive queues and resets its + * driver. The service will be automatically re-enabled once the partner has + * acknowledged the reset (see vs_session_handle_service_reset, above). + * + * If the given service is the core service, this will perform a transport + * reset, which implicitly resets (on the server side) or destroys (on + * the client side) every other service on the session. + * + * If the given service is already being reset, this has no effect, other + * than to delay completion of the reset if it is being throttled. + * + * For lock safety reasons, a service can only be directly reset by itself, + * the core service, or the service that created it (which is typically also + * the core service). + * + * A service that wishes to reset itself must not do so while holding its state + * lock or while running on its own workqueue. In these circumstances, call + * vs_service_reset_nosync() instead. Note that returning an error code + * (any negative number) from a driver callback forces a call to + * vs_service_reset_nosync() and prints an error message. + */ +int vs_service_reset(struct vs_service_device *service, + struct vs_service_device *caller) +{ + struct vs_session_device *session = vs_service_get_session(service); + + if (caller != service && caller != service->owner) { + struct vs_service_device *core_service = session->core_service; + + WARN_ON(!core_service); + if (caller != core_service) + return -EPERM; + } + + reset_service(service); + /* reset_service returns with ready_lock held, but we don't need it */ + mutex_unlock(&service->ready_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(vs_service_reset); + +/** + * vs_service_reset_nosync - asynchronously reset a service. + * @service: the service that is to be reset + * + * This routine triggers a reset for the nominated service. It may be called + * from any context, including interrupt context. It does not wait for the + * reset to occur, and provides no synchronisation guarantees when called from + * outside the target service. + * + * This is intended only for service drivers that need to reset themselves + * from a context that would not normally allow it. In other cases, use + * vs_service_reset. + */ +void vs_service_reset_nosync(struct vs_service_device *service) +{ + service->pending_reset = true; + schedule_work(&service->reset_work); +} +EXPORT_SYMBOL_GPL(vs_service_reset_nosync); + +static void +vs_service_remove_sysfs_entries(struct vs_session_device *session, + struct vs_service_device *service) +{ + sysfs_remove_link(session->sysfs_entry, service->sysfs_name); + sysfs_remove_link(&service->dev.kobj, VS_SESSION_SYMLINK_NAME); +} + +static void vs_session_release_service_id(struct vs_service_device *service) +{ + struct vs_session_device *session = vs_service_get_session(service); + + mutex_lock(&session->service_idr_lock); + idr_remove(&session->service_idr, service->id); + mutex_unlock(&session->service_idr_lock); + vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, + "service id deallocated\n"); +} + +static void destroy_service(struct vs_service_device *service, + bool notify_remote) +{ + struct vs_session_device *session = vs_service_get_session(service); + struct vs_session_driver *session_drv = + to_vs_session_driver(session->dev.driver); + struct vs_service_device *core_service __maybe_unused = + session->core_service; + int err; + + lockdep_assert_held(&service->ready_lock); + WARN_ON(service->readiness != VS_SERVICE_DELETED); + + /* Notify the core service and transport that the service is gone */ + session->transport->vt->service_remove(session->transport, service); + if (notify_remote && service->id && session_drv->service_removed) { + err = session_drv->service_removed(session, service); + if (err < 0) { + dev_err(&session->dev, + "Failed to remove service %d: %d\n", + service->id, err); + session_fatal_error(session, GFP_KERNEL); + } + } + + /* + * At this point the service is guaranteed to be gone on the client + * side, so we can safely release the service ID. + */ + if (session->is_server) + vs_session_release_service_id(service); + + /* + * This guarantees that any concurrent vs_session_get_service() that + * found the service before we removed it from the IDR will take a + * reference before we release ours. + * + * This similarly protects for_each_[usable_]service(). + */ + synchronize_rcu(); + + /* Matches device_initialize() in vs_service_register() */ + put_device(&service->dev); +} + +/** + * disable_service - prevent a service becoming ready + * @service: the service that is to be disabled + * @force: true if the service is known to be in reset + * + * This routine may be called for any inactive service. Once disabled, the + * service cannot be made ready by the session, and thus cannot become active, + * until vs_service_enable() is called for it. If multiple calls are made to + * this function, they must be balanced by vs_service_enable() calls. + * + * If the force option is true, then any pending unacknowledged reset will be + * presumed to have been acknowledged. This is used when the core service is + * entering reset. + * + * This is used by the core service client to prevent the service restarting + * until the server is ready (i.e., a server_ready message is received); by + * the session layer to stop all communication while the core service itself + * is in reset; and by the transport layer when the transport was unable to + * complete reset of a service in its reset callback (typically because + * a service had passed message buffers to another Linux subsystem and could + * not free them immediately). + * + * In any case, there is no need for the operation to be signalled in any + * way, because the service is already in reset. It simply delays future + * signalling of service readiness. + */ +static void disable_service(struct vs_service_device *service, bool force) +{ + lockdep_assert_held(&service->ready_lock); + + switch(service->readiness) { + case VS_SERVICE_INIT: + case VS_SERVICE_DELETED: + case VS_SERVICE_LOCAL_DELETE: + dev_err(&service->dev, "disabled while uninitialised\n"); + break; + case VS_SERVICE_ACTIVE: + dev_err(&service->dev, "disabled while active\n"); + break; + case VS_SERVICE_LOCAL_RESET: + /* + * Will go to DISABLED state when reset completes, unless + * it's being forced (i.e. we're moving to a core protocol + * state that implies everything else is reset). + */ + if (force) + service->readiness = VS_SERVICE_DISABLED; + service->disable_count++; + break; + default: + service->readiness = VS_SERVICE_DISABLED; + service->disable_count++; + break; + } + + cancel_delayed_work(&service->ready_work); +} + +static int service_handle_reset(struct vs_session_device *session, + struct vs_service_device *target, bool disable) +{ + struct vs_session_driver *session_drv = + to_vs_session_driver(session->dev.driver); + int err = 0; + + mutex_lock_nested(&target->ready_lock, target->lock_subclass); + + switch (target->readiness) { + case VS_SERVICE_LOCAL_DELETE: + target->readiness = VS_SERVICE_DELETED; + destroy_service(target, true); + break; + case VS_SERVICE_ACTIVE: + /* + * Reset the service and send a reset notification. + * + * We only send notifications for non-core services. This is + * because core notifies by sending a transport reset, which + * is what brought us here in the first place. Note that we + * must already hold the core service state lock iff the + * target is non-core. + */ + target->last_reset_request = jiffies; + __reset_service(target, target->id != 0); + /* fall through */ + case VS_SERVICE_LOCAL_RESET: + target->readiness = target->disable_count ? + VS_SERVICE_DISABLED : VS_SERVICE_RESET; + if (disable) + disable_service(target, false); + if (target->readiness != VS_SERVICE_DISABLED) + queue_ready_work(target); + break; + case VS_SERVICE_READY: + /* Tell userspace that the service is no longer ready. */ + kobject_uevent(&target->dev.kobj, KOBJ_OFFLINE); + /* fall through */ + case VS_SERVICE_RESET: + /* + * This can happen for a non-core service if we get a reset + * request from the server on the client side, after the + * client has enabled the service but before it is active. + * Note that the service is already active on the server side + * at this point. The client's delay may be due to either + * reset throttling or the absence of a driver. + * + * We bump the reset request timestamp, disable the service + * again, and send back an acknowledgement. + */ + if (disable && target->id) { + target->last_reset_request = jiffies; + + err = session_drv->service_local_reset( + session, target); + if (err < 0) { + dev_err(&session->dev, + "Failed to reset service %d; %d\n", + target->id, err); + session_fatal_error(session, + GFP_KERNEL); + } + + disable_service(target, false); + break; + } + /* fall through */ + case VS_SERVICE_DISABLED: + /* + * This can happen for the core service if we get a reset + * before the transport has activated, or before the core + * service has become ready. + * + * We bump the reset request timestamp, and disable the + * service again if the transport had already activated and + * enabled it. + */ + if (disable && !target->id) { + target->last_reset_request = jiffies; + + if (target->readiness != VS_SERVICE_DISABLED) + disable_service(target, false); + + break; + } + /* fall through */ + default: + dev_warn(&target->dev, "remote reset while inactive (%d)\n", + target->readiness); + err = -EPROTO; + break; + } + + mutex_unlock(&target->ready_lock); + return err; +} + +/** + * vs_service_handle_reset - handle an incoming notification of a reset + * @session: the session that owns the service + * @service_id: the ID of the service that is to be reset + * @disable: if true, the service will not be automatically re-enabled + * + * This routine is called by the core service when the remote end notifies us + * of a non-core service reset. The service must be in ACTIVE, LOCAL_RESET or + * LOCAL_DELETED state. It must be called with the core service's state lock + * held. + * + * If the service was in ACTIVE state, the core service is called back to send + * a notification to the other end. If it was in LOCAL_DELETED state, it is + * unregistered. + */ +int vs_service_handle_reset(struct vs_session_device *session, + vs_service_id_t service_id, bool disable) +{ + struct vs_service_device *target; + int ret; + + if (!service_id) + return -EINVAL; + + target = vs_session_get_service(session, service_id); + if (!target) + return -ENODEV; + + ret = service_handle_reset(session, target, disable); + vs_put_service(target); + return ret; +} +EXPORT_SYMBOL_GPL(vs_service_handle_reset); + +static int __enable_service(struct vs_service_device *service) +{ + if (WARN_ON(!service->disable_count)) + return -EINVAL; + + if (--service->disable_count > 0) + return 0; + + /* + * If the service is still resetting, it can't become ready until the + * reset completes. If it has been deleted, it will never become + * ready. In either case, there's nothing more to do. + */ + if ((service->readiness == VS_SERVICE_LOCAL_RESET) || + (service->readiness == VS_SERVICE_LOCAL_DELETE) || + (service->readiness == VS_SERVICE_DELETED)) + return 0; + + if (WARN_ON(service->readiness != VS_SERVICE_DISABLED)) + return -EINVAL; + + service->readiness = VS_SERVICE_RESET; + service->last_reset = jiffies; + queue_ready_work(service); + + return 0; +} + +/** + * vs_service_enable - allow a service to become ready + * @service: the service that is to be enabled + * + * Calling this routine for a service permits the session layer to make the + * service ready. It will do so as soon as any outstanding reset throttling + * is complete, and will then start the service once it has a driver attached. + * + * Services are disabled, requiring a call to this routine to re-enable them: + * - when first initialised (after vs_service_start), + * - when reset on the client side by vs_service_handle_reset, + * - when the transport has delayed completion of a reset, and + * - when the server-side core protocol is disconnected or reset by + * vs_session_disable_noncore. + */ +int vs_service_enable(struct vs_service_device *service) +{ + int ret; + + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + ret = __enable_service(service); + + mutex_unlock(&service->ready_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(vs_service_enable); + +/* + * Service work functions + */ +static void queue_rx_work(struct vs_service_device *service) +{ + bool rx_atomic; + + rx_atomic = vs_service_has_atomic_rx(service); + vs_dev_debug(VS_DEBUG_SESSION, vs_service_get_session(service), + &service->dev, "Queuing rx %s\n", + rx_atomic ? "tasklet (atomic)" : "work (cansleep)"); + + if (rx_atomic) + tasklet_schedule(&service->rx_tasklet); + else + queue_work(service->work_queue, &service->rx_work); +} + +static void cancel_pending_rx(struct vs_service_device *service) +{ + struct vs_mbuf *mbuf; + + lockdep_assert_held(&service->ready_lock); + + cancel_work_sync(&service->rx_work); + tasklet_kill(&service->rx_tasklet); + + spin_lock_irq(&service->rx_lock); + while (!list_empty(&service->rx_queue)) { + mbuf = list_first_entry(&service->rx_queue, + struct vs_mbuf, queue); + list_del_init(&mbuf->queue); + spin_unlock_irq(&service->rx_lock); + vs_service_free_mbuf(service, mbuf); + spin_lock_irq(&service->rx_lock); + } + service->tx_ready = false; + spin_unlock_irq(&service->rx_lock); +} + +static bool reset_throttle_cooled_off(struct vs_service_device *service); +static unsigned long reset_cool_off(struct vs_service_device *service); + +static void service_cooloff_work(struct work_struct *work) +{ + struct vs_service_device *service = container_of(work, + struct vs_service_device, cooloff_work.work); + struct vs_session_device *session = vs_service_get_session(service); + unsigned long current_time = jiffies, wake_time; + + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + if (reset_throttle_cooled_off(service)) { + vs_debug(VS_DEBUG_SESSION, session, + "Reset thrashing cooled off (delay = %u ms, cool off = %u ms, last reset %u ms ago, last reset request was %u ms ago)\n", + jiffies_to_msecs(service->reset_delay), + jiffies_to_msecs(reset_cool_off(service)), + msecs_ago(service->last_reset), + msecs_ago(service->last_reset_request)); + + service->reset_delay = 0; + + /* + * If the service is already in reset, then queue_ready_work + * has already run and has deferred queuing of the ready_work + * until cooloff. Schedule the ready work to run immediately. + */ + if (service->readiness == VS_SERVICE_RESET) + schedule_delayed_work(&service->ready_work, 0); + } else { + /* + * This can happen if last_reset_request has been bumped + * since the cooloff work was first queued. We need to + * work out how long it is until the service cools off, + * then reschedule ourselves. + */ + wake_time = reset_cool_off(service) + + service->last_reset_request; + + WARN_ON(time_after(current_time, wake_time)); + + schedule_delayed_work(&service->cooloff_work, + wake_time - current_time); + } + + mutex_unlock(&service->ready_lock); +} + +static void +service_reset_work(struct work_struct *work) +{ + struct vs_service_device *service = container_of(work, + struct vs_service_device, reset_work); + + service->pending_reset = false; + + vs_service_reset(service, service); +} + +/* Returns true if there are more messages to handle */ +static bool +dequeue_and_handle_received_message(struct vs_service_device *service) +{ + struct vs_service_driver *driver = + to_vs_service_driver(service->dev.driver); + struct vs_session_device *session = vs_service_get_session(service); + const struct vs_transport_vtable *vt = session->transport->vt; + struct vs_service_stats *stats = &service->stats; + struct vs_mbuf *mbuf; + size_t size; + int ret; + + /* Don't do rx work unless the service is active */ + if (service->readiness != VS_SERVICE_ACTIVE) + return false; + + /* Atomically take an item from the queue */ + spin_lock_irq(&service->rx_lock); + if (!list_empty(&service->rx_queue)) { + mbuf = list_first_entry(&service->rx_queue, struct vs_mbuf, + queue); + list_del_init(&mbuf->queue); + spin_unlock_irq(&service->rx_lock); + size = vt->mbuf_size(mbuf); + + /* + * Call the message handler for the service. The service's + * message handler is responsible for freeing the mbuf when it + * is done with it. + */ + ret = driver->receive(service, mbuf); + if (ret < 0) { + atomic_inc(&service->stats.recv_failures); + dev_err(&service->dev, + "receive returned %d; resetting service\n", + ret); + vs_service_reset_nosync(service); + return false; + } else { + atomic_add(size, &service->stats.recv_bytes); + atomic_inc(&service->stats.recv_mbufs); + } + + } else if (service->tx_ready) { + service->tx_ready = false; + spin_unlock_irq(&service->rx_lock); + + /* + * Update the tx_ready stats accounting and then call the + * service's tx_ready handler. + */ + atomic_inc(&stats->nr_tx_ready); + if (atomic_read(&stats->nr_over_quota) > 0) { + int total; + + total = atomic_add_return(jiffies_to_msecs(jiffies - + stats->over_quota_time), + &stats->over_quota_time_total); + atomic_set(&stats->over_quota_time_avg, total / + atomic_read(&stats->nr_over_quota)); + } + atomic_set(&service->is_over_quota, 0); + + /* + * Note that a service's quota may reduce at any point, even + * during the tx_ready handler. This is important if a service + * has an ordered list of pending messages to send. If a + * message fails to send from the tx_ready handler due to + * over-quota then subsequent messages in the same handler may + * send successfully. To avoid sending messages in the + * incorrect order the service's tx_ready handler should + * return immediately if a message fails to send. + */ + ret = driver->tx_ready(service); + if (ret < 0) { + dev_err(&service->dev, + "tx_ready returned %d; resetting service\n", + ret); + vs_service_reset_nosync(service); + return false; + } + } else { + spin_unlock_irq(&service->rx_lock); + } + + /* + * There's no need to lock for this list_empty: if we race + * with a msg enqueue, we'll be rescheduled by the other side, + * and if we race with a dequeue, we'll just do nothing when + * we run (or will be cancelled before we run). + */ + return !list_empty(&service->rx_queue) || service->tx_ready; +} + +static void service_rx_tasklet(unsigned long data) +{ + struct vs_service_device *service = (struct vs_service_device *)data; + bool resched; + + /* + * There is no need to acquire the state spinlock or mutex here, + * because this tasklet is disabled when the lock is held. These + * are annotations for sparse and lockdep, respectively. + * + * We can't annotate the implicit mutex acquire because lockdep gets + * upset about inconsistent softirq states. + */ + __acquire(service); + spin_acquire(&service->state_spinlock.dep_map, 0, 0, _THIS_IP_); + + resched = dequeue_and_handle_received_message(service); + + if (resched) + tasklet_schedule(&service->rx_tasklet); + + spin_release(&service->state_spinlock.dep_map, 0, _THIS_IP_); + __release(service); +} + +static void service_rx_work(struct work_struct *work) +{ + struct vs_service_device *service = container_of(work, + struct vs_service_device, rx_work); + bool requeue; + + /* + * We must acquire the state mutex here to protect services that + * are using vs_service_state_lock(). + * + * There is no need to acquire the spinlock, which is never used in + * drivers with task context receive handlers. + */ + vs_service_state_lock(service); + + requeue = dequeue_and_handle_received_message(service); + + vs_service_state_unlock(service); + + if (requeue) + queue_work(service->work_queue, work); +} + +/* + * Service sysfs statistics counters. These files are all atomic_t, and + * read only, so we use a generator macro to avoid code duplication. + */ +#define service_stat_attr(__name) \ + static ssize_t service_stat_##__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, "%u\n", \ + atomic_read(&service->stats.__name)); \ + } \ + static DEVICE_ATTR(__name, S_IRUGO, \ + service_stat_##__name##_show, NULL); + +service_stat_attr(sent_mbufs); +service_stat_attr(sent_bytes); +service_stat_attr(recv_mbufs); +service_stat_attr(recv_bytes); +service_stat_attr(nr_over_quota); +service_stat_attr(nr_tx_ready); +service_stat_attr(over_quota_time_total); +service_stat_attr(over_quota_time_avg); + +static struct attribute *service_stat_dev_attrs[] = { + &dev_attr_sent_mbufs.attr, + &dev_attr_sent_bytes.attr, + &dev_attr_recv_mbufs.attr, + &dev_attr_recv_bytes.attr, + &dev_attr_nr_over_quota.attr, + &dev_attr_nr_tx_ready.attr, + &dev_attr_over_quota_time_total.attr, + &dev_attr_over_quota_time_avg.attr, + NULL, +}; + +static const struct attribute_group service_stat_attributes = { + .name = "stats", + .attrs = service_stat_dev_attrs, +}; + +static void delete_service(struct vs_service_device *service) +{ + struct vs_session_device *session = vs_service_get_session(service); + bool notify_on_destroy = true; + + /* FIXME: Jira ticket SDK-3495 - philipd. */ + /* This should be the caller's responsibility */ + vs_get_service(service); + + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + /* + * If we're on the client side, the service should already have been + * disabled at this point. + */ + WARN_ON(service->id != 0 && !session->is_server && + service->readiness != VS_SERVICE_DISABLED && + service->readiness != VS_SERVICE_DELETED); + + /* + * Make sure the service is not active, and notify the remote end if + * it needs to be reset. Note that we already hold the core service + * state lock iff this is a non-core service. + */ + __reset_service(service, true); + + /* + * If the remote end is aware that the service is inactive, we can + * delete right away; otherwise we need to wait for a notification + * that the service has reset. + */ + switch (service->readiness) { + case VS_SERVICE_LOCAL_DELETE: + case VS_SERVICE_DELETED: + /* Nothing to do here */ + mutex_unlock(&service->ready_lock); + vs_put_service(service); + return; + case VS_SERVICE_ACTIVE: + BUG(); + break; + case VS_SERVICE_LOCAL_RESET: + service->readiness = VS_SERVICE_LOCAL_DELETE; + break; + case VS_SERVICE_INIT: + notify_on_destroy = false; + /* Fall through */ + default: + service->readiness = VS_SERVICE_DELETED; + destroy_service(service, notify_on_destroy); + break; + } + + mutex_unlock(&service->ready_lock); + + /* + * Remove service syslink from + * sys/vservices/(/)-sessions/ directory + */ + vs_service_remove_sysfs_entries(session, service); + + sysfs_remove_group(&service->dev.kobj, &service_stat_attributes); + + /* + * On the client-side we need to release the service id as soon as + * the service is deleted. Otherwise the server may attempt to create + * a new service with this id. + */ + if (!session->is_server) + vs_session_release_service_id(service); + + device_del(&service->dev); + vs_put_service(service); +} + +/** + * vs_service_delete - deactivate and start removing a service device + * @service: the service to delete + * @caller: the service initiating deletion + * + * Services may only be deleted by their owner (on the server side), or by the + * core service. This function must not be called for the core service. + */ +int vs_service_delete(struct vs_service_device *service, + struct vs_service_device *caller) +{ + struct vs_session_device *session = + vs_service_get_session(service); + struct vs_service_device *core_service = session->core_service; + + if (WARN_ON(!core_service)) + return -ENODEV; + + if (!service->id) + return -EINVAL; + + if (caller != service->owner && caller != core_service) + return -EPERM; + + delete_service(service); + + return 0; +} +EXPORT_SYMBOL_GPL(vs_service_delete); + +/** + * vs_service_handle_delete - deactivate and start removing a service device + * @service: the service to delete + * + * This is a variant of vs_service_delete which must only be called by the + * core service. It is used by the core service client when a service_removed + * message is received. + */ +int vs_service_handle_delete(struct vs_service_device *service) +{ + struct vs_session_device *session __maybe_unused = + vs_service_get_session(service); + struct vs_service_device *core_service __maybe_unused = + session->core_service; + + lockdep_assert_held(&core_service->state_mutex); + + delete_service(service); + + return 0; +} +EXPORT_SYMBOL_GPL(vs_service_handle_delete); + +static void service_cleanup_work(struct work_struct *work) +{ + struct vs_service_device *service = container_of(work, + struct vs_service_device, cleanup_work); + struct vs_session_device *session = vs_service_get_session(service); + + vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, "cleanup\n"); + + if (service->owner) + vs_put_service(service->owner); + + /* Put our reference to the session */ + if (service->dev.parent) + put_device(service->dev.parent); + + tasklet_kill(&service->rx_tasklet); + cancel_work_sync(&service->rx_work); + cancel_delayed_work_sync(&service->cooloff_work); + cancel_delayed_work_sync(&service->ready_work); + cancel_work_sync(&service->reset_work); + + if (service->work_queue) + destroy_workqueue(service->work_queue); + + kfree(service->sysfs_name); + kfree(service->name); + kfree(service->protocol); + kfree(service); +} + +static void vs_service_release(struct device *dev) +{ + struct vs_service_device *service = to_vs_service_device(dev); + + vs_dev_debug(VS_DEBUG_SESSION, vs_service_get_session(service), + &service->dev, "release\n"); + + /* + * We need to defer cleanup to avoid a circular dependency between the + * core service's state lock (which can be held at this point, on the + * client side) and any non-core service's reset work (which we must + * cancel here, and which acquires the core service state lock). + */ + schedule_work(&service->cleanup_work); +} + +static int service_add_idr(struct vs_session_device *session, + struct vs_service_device *service, vs_service_id_t service_id) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0) + int err, base_id, id; + + if (service_id == VS_SERVICE_AUTO_ALLOCATE_ID) + base_id = 1; + else + base_id = service_id; + +retry: + if (!idr_pre_get(&session->service_idr, GFP_KERNEL)) + return -ENOMEM; + + mutex_lock(&session->service_idr_lock); + err = idr_get_new_above(&session->service_idr, service, base_id, &id); + if (err == 0) { + if (service_id != VS_SERVICE_AUTO_ALLOCATE_ID && + id != service_id) { + /* Failed to allocated the requested service id */ + idr_remove(&session->service_idr, id); + mutex_unlock(&session->service_idr_lock); + return -EBUSY; + } + if (id > VS_MAX_SERVICE_ID) { + /* We are out of service ids */ + idr_remove(&session->service_idr, id); + mutex_unlock(&session->service_idr_lock); + return -ENOSPC; + } + } + mutex_unlock(&session->service_idr_lock); + if (err == -EAGAIN) + goto retry; + if (err < 0) + return err; +#else + int start, end, id; + + if (service_id == VS_SERVICE_AUTO_ALLOCATE_ID) { + start = 1; + end = VS_MAX_SERVICES; + } else { + start = service_id; + end = service_id + 1; + } + + mutex_lock(&session->service_idr_lock); + id = idr_alloc(&session->service_idr, service, start, end, + GFP_KERNEL); + mutex_unlock(&session->service_idr_lock); + + if (id == -ENOSPC) + return -EBUSY; + else if (id < 0) + return id; +#endif + + service->id = id; + return 0; +} + +static int +vs_service_create_sysfs_entries(struct vs_session_device *session, + struct vs_service_device *service, vs_service_id_t id) +{ + int ret; + char *sysfs_name, *c; + + /* Add a symlink to session device inside service device sysfs */ + ret = sysfs_create_link(&service->dev.kobj, &session->dev.kobj, + VS_SESSION_SYMLINK_NAME); + if (ret) { + dev_err(&service->dev, "Error %d creating session symlink\n", + ret); + goto fail; + } + + /* Get the length of the string for sysfs dir */ + sysfs_name = kasprintf(GFP_KERNEL, "%s:%d", service->name, id); + if (!sysfs_name) { + ret = -ENOMEM; + goto fail_session_link; + } + + /* + * We dont want to create symlinks with /'s which could get interpreted + * as another directory so replace all /'s with !'s + */ + while ((c = strchr(sysfs_name, '/'))) + *c = '!'; + ret = sysfs_create_link(session->sysfs_entry, &service->dev.kobj, + sysfs_name); + if (ret) + goto fail_free_sysfs_name; + + service->sysfs_name = sysfs_name; + + return 0; + +fail_free_sysfs_name: + kfree(sysfs_name); +fail_session_link: + sysfs_remove_link(&service->dev.kobj, VS_SESSION_SYMLINK_NAME); +fail: + return ret; +} + +/** + * vs_service_register - create and register a new vs_service_device + * @session: the session device that is the parent of the service + * @owner: the service responsible for managing the new service + * @service_id: the ID of the new service + * @name: the name of the new service + * @protocol: the protocol for the new service + * @plat_data: value to be assigned to (struct device *)->platform_data + * + * This function should only be called by a session driver that is bound to + * the given session. + * + * The given service_id must not have been passed to a prior successful + * vs_service_register call, unless the service ID has since been freed by a + * call to the session driver's service_removed callback. + * + * The core service state lock must not be held while calling this function. + */ +struct vs_service_device *vs_service_register(struct vs_session_device *session, + struct vs_service_device *owner, vs_service_id_t service_id, + const char *protocol, const char *name, const void *plat_data) +{ + struct vs_service_device *service; + struct vs_session_driver *session_drv; + int ret = -EIO; + char *c; + + if (service_id && !owner) { + dev_err(&session->dev, "Non-core service must have an owner\n"); + ret = -EINVAL; + goto fail; + } else if (!service_id && owner) { + dev_err(&session->dev, "Core service must not have an owner\n"); + ret = -EINVAL; + goto fail; + } + + if (!session->dev.driver) + goto fail; + + session_drv = to_vs_session_driver(session->dev.driver); + + service = kzalloc(sizeof(*service), GFP_KERNEL); + if (!service) { + ret = -ENOMEM; + goto fail; + } + + INIT_LIST_HEAD(&service->rx_queue); + INIT_WORK(&service->rx_work, service_rx_work); + INIT_WORK(&service->reset_work, service_reset_work); + INIT_DELAYED_WORK(&service->ready_work, service_ready_work); + INIT_DELAYED_WORK(&service->cooloff_work, service_cooloff_work); + INIT_WORK(&service->cleanup_work, service_cleanup_work); + spin_lock_init(&service->rx_lock); + init_waitqueue_head(&service->quota_wq); + + service->owner = vs_get_service(owner); + + service->readiness = VS_SERVICE_INIT; + mutex_init(&service->ready_lock); + service->driver_probed = false; + + /* + * Service state locks - A service is only allowed to use one of these + */ + spin_lock_init(&service->state_spinlock); + mutex_init(&service->state_mutex); +#ifdef CONFIG_VSERVICES_LOCK_DEBUG + service->state_spinlock_used = false; + service->state_mutex_used = false; +#endif + + /* Lock ordering + * + * The dependency order for the various service locks is as follows: + * + * cooloff_work + * reset_work + * ready_work + * ready_lock/0 + * rx_work/0 + * state_mutex/0 + * ready_lock/1 + * ... + * state_mutex/n + * state_spinlock + * + * The subclass is the service's rank in the hierarchy of + * service ownership. This results in core having subclass 0 on + * server-side and 1 on client-side. Services directly created + * by the core will have a lock subclass value of 2 for + * servers, 3 for clients. Services created by non-core + * services will have a lock subclass value of x + 1, where x + * is the lock subclass of the creator service. (e.g servers + * will have even numbered lock subclasses, clients will have + * odd numbered lock subclasses). + * + * If a service driver has any additional locks for protecting + * internal state, they will generally fit between state_mutex/n and + * ready_lock/n+1 on this list. For the core service, this applies to + * the session lock. + */ + + if (owner) + service->lock_subclass = owner->lock_subclass + 2; + else + service->lock_subclass = session->is_server ? 0 : 1; + +#ifdef CONFIG_LOCKDEP + if (service->lock_subclass >= MAX_LOCKDEP_SUBCLASSES) { + dev_warn(&session->dev, "Owner hierarchy is too deep, lockdep will fail\n"); + } else { + /* + * We need to set the default subclass for the rx work, + * because the workqueue API doesn't (and can't) provide + * anything like lock_nested() for it. + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + /* + * Lockdep allows a specific lock's subclass to be set with + * the subclass argument to lockdep_init_map(). However, prior + * to Linux 3.3, that only works the first time it is called + * for a given class and subclass. So we have to fake it, + * putting every subclass in a different class, so the only + * thing that breaks is printing the subclass in lockdep + * warnings. + */ + static struct lock_class_key + rx_work_keys[MAX_LOCKDEP_SUBCLASSES]; + struct lock_class_key *key = + &rx_work_keys[service->lock_subclass]; +#else + struct lock_class_key *key = service->rx_work.lockdep_map.key; +#endif + + /* + * We can't use the lockdep_set_class() macro because the + * work's lockdep map is called .lockdep_map instead of + * .dep_map. + */ + lockdep_init_map(&service->rx_work.lockdep_map, + "&service->rx_work", key, + service->lock_subclass); + } +#endif + + /* + * Copy the protocol and name. Remove any leading or trailing + * whitespace characters (including newlines) since the strings + * may have been passed via sysfs files. + */ + if (protocol) { + service->protocol = kstrdup(protocol, GFP_KERNEL); + if (!service->protocol) { + ret = -ENOMEM; + goto fail_copy_protocol; + } + c = strim(service->protocol); + if (c != service->protocol) + memmove(service->protocol, c, + strlen(service->protocol) + 1); + } + + service->name = kstrdup(name, GFP_KERNEL); + if (!service->name) { + ret = -ENOMEM; + goto fail_copy_name; + } + c = strim(service->name); + if (c != service->name) + memmove(service->name, c, strlen(service->name) + 1); + + service->is_server = session_drv->is_server; + + /* Grab a reference to the session we are on */ + service->dev.parent = get_device(&session->dev); + service->dev.bus = session_drv->service_bus; + service->dev.release = vs_service_release; + + service->last_reset = 0; + service->last_reset_request = 0; + service->last_ready = 0; + service->reset_delay = 0; + + device_initialize(&service->dev); + service->dev.platform_data = (void *)plat_data; + + ret = service_add_idr(session, service, service_id); + if (ret) + goto fail_add_idr; + +#ifdef CONFIG_VSERVICES_NAMED_DEVICE + /* Integrate session and service names in vservice devnodes */ + dev_set_name(&service->dev, "vservice-%s:%s:%s:%d:%d", + session->is_server ? "server" : "client", + session->name, service->name, + session->session_num, service->id); +#else + dev_set_name(&service->dev, "%s:%d", dev_name(&session->dev), + service->id); +#endif + +#ifdef CONFIG_VSERVICES_CHAR_DEV + if (service->id > 0) + service->dev.devt = MKDEV(vservices_cdev_major, + (session->session_num * VS_MAX_SERVICES) + + service->id); +#endif + + service->work_queue = vs_create_workqueue(dev_name(&service->dev)); + if (!service->work_queue) { + ret = -ENOMEM; + goto fail_create_workqueue; + } + + tasklet_init(&service->rx_tasklet, service_rx_tasklet, + (unsigned long)service); + + /* + * If this is the core service, set the core service pointer in the + * session. + */ + if (service->id == 0) { + mutex_lock(&session->service_idr_lock); + if (session->core_service) { + ret = -EEXIST; + mutex_unlock(&session->service_idr_lock); + goto fail_become_core; + } + + /* Put in vs_session_bus_remove() */ + session->core_service = vs_get_service(service); + mutex_unlock(&session->service_idr_lock); + } + + /* Notify the transport */ + ret = session->transport->vt->service_add(session->transport, service); + if (ret) { + dev_err(&session->dev, + "Failed to add service %d (%s:%s) to transport: %d\n", + service->id, service->name, + service->protocol, ret); + goto fail_transport_add; + } + + /* Delay uevent until vs_service_start(). */ + dev_set_uevent_suppress(&service->dev, true); + + ret = device_add(&service->dev); + if (ret) + goto fail_device_add; + + /* Create the service statistics sysfs group */ + ret = sysfs_create_group(&service->dev.kobj, &service_stat_attributes); + if (ret) + goto fail_sysfs_create_group; + + /* Create additional sysfs files */ + ret = vs_service_create_sysfs_entries(session, service, service->id); + if (ret) + goto fail_sysfs_add_entries; + + return service; + +fail_sysfs_add_entries: + sysfs_remove_group(&service->dev.kobj, &service_stat_attributes); +fail_sysfs_create_group: + device_del(&service->dev); +fail_device_add: + session->transport->vt->service_remove(session->transport, service); +fail_transport_add: + if (service->id == 0) { + session->core_service = NULL; + vs_put_service(service); + } +fail_become_core: +fail_create_workqueue: + vs_session_release_service_id(service); +fail_add_idr: + /* + * device_initialize() has been called, so we must call put_device() + * and let vs_service_release() handle the rest of the cleanup. + */ + put_device(&service->dev); + return ERR_PTR(ret); + +fail_copy_name: + kfree(service->protocol); +fail_copy_protocol: + kfree(service); +fail: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(vs_service_register); + +/** + * vs_session_get_service - Look up a service by ID on a session and get + * a reference to it. The caller must call vs_put_service when it is finished + * with the service. + * + * @session: The session to search for the service on + * @service_id: ID of the service to find + */ +struct vs_service_device * +vs_session_get_service(struct vs_session_device *session, + vs_service_id_t service_id) +{ + struct vs_service_device *service; + + if (!session) + return NULL; + + rcu_read_lock(); + service = idr_find(&session->service_idr, service_id); + if (!service) { + rcu_read_unlock(); + return NULL; + } + vs_get_service(service); + rcu_read_unlock(); + + return service; +} +EXPORT_SYMBOL_GPL(vs_session_get_service); + +/** + * __for_each_service - Iterate over all non-core services on a session. + * + * @session: Session to iterate services on + * @func: Callback function for each iterated service + * + * Iterate over all services on a session, excluding the core service, and + * call a callback function on each. + */ +static void __for_each_service(struct vs_session_device *session, + void (*func)(struct vs_service_device *)) +{ + struct vs_service_device *service; + int id; + + for (id = 1; ; id++) { + rcu_read_lock(); + service = idr_get_next(&session->service_idr, &id); + if (!service) { + rcu_read_unlock(); + break; + } + vs_get_service(service); + rcu_read_unlock(); + + func(service); + vs_put_service(service); + } +} + +/** + * vs_session_delete_noncore - immediately delete all non-core services + * @session: the session whose services are to be deleted + * + * This function disables and deletes all non-core services without notifying + * the core service. It must only be called by the core service, with its state + * lock held. It is used when the core service client disconnects or + * resets, and when the core service server has its driver removed. + */ +void vs_session_delete_noncore(struct vs_session_device *session) +{ + struct vs_service_device *core_service __maybe_unused = + session->core_service; + + lockdep_assert_held(&core_service->state_mutex); + + vs_session_disable_noncore(session); + + __for_each_service(session, delete_service); +} +EXPORT_SYMBOL_GPL(vs_session_delete_noncore); + +/** + * vs_session_for_each_service - Iterate over all initialised and non-deleted + * non-core services on a session. + * + * @session: Session to iterate services on + * @func: Callback function for each iterated service + * @data: Extra data to pass to the callback + * + * Iterate over all services on a session, excluding the core service and any + * service that has been deleted or has not yet had vs_service_start() called, + * and call a callback function on each. The callback function is called with + * the service's ready lock held. + */ +void vs_session_for_each_service(struct vs_session_device *session, + void (*func)(struct vs_service_device *, void *), void *data) +{ + struct vs_service_device *service; + int id; + + for (id = 1; ; id++) { + rcu_read_lock(); + service = idr_get_next(&session->service_idr, &id); + if (!service) { + rcu_read_unlock(); + break; + } + vs_get_service(service); + rcu_read_unlock(); + + mutex_lock_nested(&service->ready_lock, service->lock_subclass); + + if (service->readiness != VS_SERVICE_LOCAL_DELETE && + service->readiness != VS_SERVICE_DELETED && + service->readiness != VS_SERVICE_INIT) + func(service, data); + + mutex_unlock(&service->ready_lock); + vs_put_service(service); + } +} + +static void force_disable_service(struct vs_service_device *service, + void *unused) +{ + lockdep_assert_held(&service->ready_lock); + + if (service->readiness == VS_SERVICE_ACTIVE) + __reset_service(service, false); + + disable_service(service, true); +} + +/** + * vs_session_disable_noncore - immediately disable all non-core services + * @session: the session whose services are to be disabled + * + * This function must be called by the core service driver to disable all + * services, whenever it resets or is otherwise disconnected. It is called + * directly by the server-side core service, and by the client-side core + * service via vs_session_delete_noncore(). + */ +void vs_session_disable_noncore(struct vs_session_device *session) +{ + vs_session_for_each_service(session, force_disable_service, NULL); +} +EXPORT_SYMBOL_GPL(vs_session_disable_noncore); + +static void try_enable_service(struct vs_service_device *service, void *unused) +{ + lockdep_assert_held(&service->ready_lock); + + __enable_service(service); +} + +/** + * vs_session_enable_noncore - enable all disabled non-core services + * @session: the session whose services are to be enabled + * + * This function is called by the core server driver to enable all services + * when the core client connects. + */ +void vs_session_enable_noncore(struct vs_session_device *session) +{ + vs_session_for_each_service(session, try_enable_service, NULL); +} +EXPORT_SYMBOL_GPL(vs_session_enable_noncore); + +/** + * vs_session_handle_message - process an incoming message from a transport + * @session: the session that is receiving the message + * @mbuf: a buffer containing the message payload + * @service_id: the id of the service that the message was addressed to + * + * This routine will return 0 if the buffer was accepted, or a negative value + * otherwise. In the latter case the caller should free the buffer. If the + * error is fatal, this routine will reset the service. + * + * This routine may be called from interrupt context. + * + * The caller must always serialise calls to this function relative to + * vs_session_handle_reset and vs_session_handle_activate. We don't do this + * internally, to avoid having to disable interrupts when called from task + * context. + */ +int vs_session_handle_message(struct vs_session_device *session, + struct vs_mbuf *mbuf, vs_service_id_t service_id) +{ + struct vs_service_device *service; + struct vs_transport *transport; + unsigned long flags; + + transport = session->transport; + + service = vs_session_get_service(session, service_id); + if (!service) { + dev_err(&session->dev, "message for unknown service %d\n", + service_id); + session_fatal_error(session, GFP_ATOMIC); + return -ENOTCONN; + } + + /* + * Take the rx lock before checking service readiness. This guarantees + * that if __reset_service() has just made the service inactive, we + * either see it and don't enqueue the message, or else enqueue the + * message before cancel_pending_rx() runs (and removes it). + */ + spin_lock_irqsave(&service->rx_lock, flags); + + /* If the service is not active, drop the message. */ + if (service->readiness != VS_SERVICE_ACTIVE) { + spin_unlock_irqrestore(&service->rx_lock, flags); + vs_put_service(service); + return -ECONNRESET; + } + + list_add_tail(&mbuf->queue, &service->rx_queue); + spin_unlock_irqrestore(&service->rx_lock, flags); + + /* Schedule processing of the message by the service's drivers. */ + queue_rx_work(service); + vs_put_service(service); + + return 0; +} +EXPORT_SYMBOL_GPL(vs_session_handle_message); + +/** + * vs_session_quota_available - notify a service that it can transmit + * @session: the session owning the service that is ready + * @service_id: the id of the service that is ready + * @count: the number of buffers that just became ready + * @call_tx_ready: true if quota has just become nonzero due to a buffer being + * freed by the remote communication partner + * + * This routine is called by the transport driver when a send-direction + * message buffer becomes free. It wakes up any task that is waiting for + * send quota to become available. + * + * This routine may be called from interrupt context from the transport + * driver, and as such, it may not sleep. + * + * The caller must always serialise calls to this function relative to + * vs_session_handle_reset and vs_session_handle_activate. We don't do this + * internally, to avoid having to disable interrupts when called from task + * context. + * + * If the call_tx_ready argument is true, this function also schedules a + * call to the driver's tx_ready callback. Note that this never has priority + * over handling incoming messages; it will only be handled once the receive + * queue is empty. This is to increase batching of outgoing messages, and also + * to reduce the chance that an outgoing message will be dropped by the partner + * because an incoming message has already changed the state. + * + * In general, task context drivers should use the waitqueue, and softirq + * context drivers (with tx_atomic set) should use tx_ready. + */ +void vs_session_quota_available(struct vs_session_device *session, + vs_service_id_t service_id, unsigned count, + bool send_tx_ready) +{ + struct vs_service_device *service; + unsigned long flags; + + service = vs_session_get_service(session, service_id); + if (!service) { + dev_err(&session->dev, "tx ready for unknown service %d\n", + service_id); + session_fatal_error(session, GFP_ATOMIC); + return; + } + + wake_up_nr(&service->quota_wq, count); + + if (send_tx_ready) { + /* + * Take the rx lock before checking service readiness. This + * guarantees that if __reset_service() has just made the + * service inactive, we either see it and don't set the tx_ready + * flag, or else set the flag before cancel_pending_rx() runs + * (and clears it). + */ + spin_lock_irqsave(&service->rx_lock, flags); + + /* If the service is not active, drop the tx_ready event */ + if (service->readiness != VS_SERVICE_ACTIVE) { + spin_unlock_irqrestore(&service->rx_lock, flags); + vs_put_service(service); + return; + } + + service->tx_ready = true; + spin_unlock_irqrestore(&service->rx_lock, flags); + + /* Schedule RX processing by the service driver. */ + queue_rx_work(service); + } + + vs_put_service(service); +} +EXPORT_SYMBOL_GPL(vs_session_quota_available); + +/** + * vs_session_handle_notify - process an incoming notification from a transport + * @session: the session that is receiving the notification + * @flags: notification flags + * @service_id: the id of the service that the notification was addressed to + * + * This function may be called from interrupt context from the transport driver, + * and as such, it may not sleep. + */ +void vs_session_handle_notify(struct vs_session_device *session, + unsigned long bits, vs_service_id_t service_id) +{ + struct vs_service_device *service; + struct vs_service_driver *driver; + unsigned long flags; + + service = vs_session_get_service(session, service_id); + if (!service) { + /* Ignore the notification since the service id doesn't exist */ + dev_err(&session->dev, "notification for unknown service %d\n", + service_id); + return; + } + + /* + * Take the rx lock before checking service readiness. This guarantees + * that if __reset_service() has just made the service inactive, we + * either see it and don't send the notification, or else send it + * before cancel_pending_rx() runs (and thus before the driver is + * deactivated). + */ + spin_lock_irqsave(&service->rx_lock, flags); + + /* If the service is not active, drop the notification. */ + if (service->readiness != VS_SERVICE_ACTIVE) { + spin_unlock_irqrestore(&service->rx_lock, flags); + vs_put_service(service); + return; + } + + /* There should be a driver bound on the service */ + if (WARN_ON(!service->dev.driver)) { + spin_unlock_irqrestore(&service->rx_lock, flags); + vs_put_service(service); + return; + } + + driver = to_vs_service_driver(service->dev.driver); + /* Call the driver's notify function */ + driver->notify(service, bits); + + spin_unlock_irqrestore(&service->rx_lock, flags); + vs_put_service(service); +} +EXPORT_SYMBOL_GPL(vs_session_handle_notify); + +static unsigned long reset_cool_off(struct vs_service_device *service) +{ + return service->reset_delay * RESET_THROTTLE_COOL_OFF_MULT; +} + +static bool ready_needs_delay(struct vs_service_device *service) +{ + /* + * We throttle resets if too little time elapsed between the service + * last becoming ready, and the service last starting a reset. + * + * We do not use the current time here because it includes the time + * taken by the local service driver to actually process the reset. + */ + return service->last_reset && service->last_ready && time_before( + service->last_reset, + service->last_ready + RESET_THROTTLE_TIME); +} + +static bool reset_throttle_cooled_off(struct vs_service_device *service) +{ + /* + * Reset throttling cools off if enough time has elapsed since the + * last reset request. + * + * We check against the last requested reset, not the last serviced + * reset or ready. If we are throttling, a reset may not have been + * serviced for some time even though we are still receiving requests. + */ + return service->reset_delay && service->last_reset_request && + time_after(jiffies, service->last_reset_request + + reset_cool_off(service)); +} + +/* + * Queue up the ready work for a service. If a service is resetting too fast + * then it will be throttled using an exponentially increasing delay before + * marking it ready. If the reset speed backs off then the ready throttling + * will be cleared. If a service reaches the maximum throttling delay then all + * resets will be ignored until the cool off period has elapsed. + * + * The basic logic of the reset throttling is: + * + * - If a reset request is processed and the last ready was less than + * RESET_THROTTLE_TIME ago, then the ready needs to be delayed to + * throttle resets. + * + * - The ready delay increases exponentially on each throttled reset + * between RESET_THROTTLE_MIN and RESET_THROTTLE_MAX. + * + * - If RESET_THROTTLE_MAX is reached then no ready will be sent until the + * reset requests have cooled off. + * + * - Reset requests have cooled off when no reset requests have been + * received for RESET_THROTTLE_COOL_OFF_MULT * the service's current + * ready delay. The service's reset throttling is disabled. + * + * Note: Be careful when adding print statements, including debugging, to + * this function. The ready throttling is intended to prevent DOSing of the + * vServices due to repeated resets (e.g. because of a persistent failure). + * Adding a printk on each reset for example would reset in syslog spamming + * which is a DOS attack in itself. + * + * The ready lock must be held by the caller. + */ +static void queue_ready_work(struct vs_service_device *service) +{ + struct vs_session_device *session = vs_service_get_session(service); + unsigned long delay; + bool wait_for_cooloff = false; + + lockdep_assert_held(&service->ready_lock); + + /* This should only be called when the service enters reset. */ + WARN_ON(service->readiness != VS_SERVICE_RESET); + + if (ready_needs_delay(service)) { + /* Reset delay increments exponentially */ + if (!service->reset_delay) { + service->reset_delay = RESET_THROTTLE_MIN; + } else if (service->reset_delay < RESET_THROTTLE_MAX) { + service->reset_delay *= 2; + } else { + wait_for_cooloff = true; + } + + delay = service->reset_delay; + } else { + /* The reset request appears to have been be sane. */ + delay = 0; + + } + + if (service->reset_delay > 0) { + /* + * Schedule cooloff work, to set the reset_delay to 0 if + * the reset requests stop for long enough. + */ + schedule_delayed_work(&service->cooloff_work, + reset_cool_off(service)); + } + + if (wait_for_cooloff) { + /* + * We need to finish cooling off before we service resets + * again. Schedule cooloff_work to run after the current + * cooloff period ends; it may reschedule itself even later + * if any more requests arrive. + */ + dev_err(&session->dev, + "Service %s is resetting too fast - must cool off for %u ms\n", + dev_name(&service->dev), + jiffies_to_msecs(reset_cool_off(service))); + return; + } + + if (delay) + dev_err(&session->dev, + "Service %s is resetting too fast - delaying ready by %u ms\n", + dev_name(&service->dev), + jiffies_to_msecs(delay)); + + vs_debug(VS_DEBUG_SESSION, session, + "Service %s will become ready in %u ms\n", + dev_name(&service->dev), + jiffies_to_msecs(delay)); + + if (service->last_ready) + vs_debug(VS_DEBUG_SESSION, session, + "Last became ready %u ms ago\n", + msecs_ago(service->last_ready)); + if (service->reset_delay >= RESET_THROTTLE_MAX) + dev_err(&session->dev, "Service %s hit max reset throttle\n", + dev_name(&service->dev)); + + schedule_delayed_work(&service->ready_work, delay); +} + +static void session_activation_work(struct work_struct *work) +{ + struct vs_session_device *session = container_of(work, + struct vs_session_device, activation_work); + struct vs_service_device *core_service = session->core_service; + struct vs_session_driver *session_drv = + to_vs_session_driver(session->dev.driver); + int activation_state; + int ret; + + if (WARN_ON(!core_service)) + return; + + if (WARN_ON(!session_drv)) + return; + + /* + * We use an atomic to prevent duplicate activations if we race with + * an activate after a reset. This is very unlikely, but possible if + * this work item is preempted. + */ + activation_state = atomic_cmpxchg(&session->activation_state, + VS_SESSION_ACTIVATE, VS_SESSION_ACTIVE); + + switch (activation_state) { + case VS_SESSION_ACTIVATE: + vs_debug(VS_DEBUG_SESSION, session, + "core service will be activated\n"); + vs_service_enable(core_service); + break; + + case VS_SESSION_RESET: + vs_debug(VS_DEBUG_SESSION, session, + "core service will be deactivated\n"); + + /* Handle the core service reset */ + ret = service_handle_reset(session, core_service, true); + + /* Tell the transport if the reset succeeded */ + if (ret >= 0) + session->transport->vt->ready(session->transport); + else + dev_err(&session->dev, "core service reset unhandled: %d\n", + ret); + + break; + + default: + vs_debug(VS_DEBUG_SESSION, session, + "core service already active\n"); + break; + } +} + +/** + * vs_session_handle_reset - Handle a reset at the session layer. + * @session: Session to reset + * + * This function is called by the transport when it receives a transport-level + * reset notification. + * + * After a session is reset by calling this function, it will reset all of its + * attached services, and then call the transport's ready callback. The + * services will remain in reset until the session is re-activated by a call + * to vs_session_handle_activate(). + * + * Calling this function on a session that is already reset is permitted, as + * long as the transport accepts the consequent duplicate ready callbacks. + * + * A newly created session is initially in the reset state, and will not call + * the transport's ready callback. The transport may choose to either act as + * if the ready callback had been called, or call this function again to + * trigger a new ready callback. + */ +void vs_session_handle_reset(struct vs_session_device *session) +{ + atomic_set(&session->activation_state, VS_SESSION_RESET); + + schedule_work(&session->activation_work); +} +EXPORT_SYMBOL_GPL(vs_session_handle_reset); + +/** + * vs_session_handle_activate - Allow a session to leave the reset state. + * @session: Session to mark active. + * + * This function is called by the transport when a transport-level reset is + * completed; that is, after the session layer has reset its services and + * called the ready callback, at *both* ends of the connection. + */ +void vs_session_handle_activate(struct vs_session_device *session) +{ + atomic_set(&session->activation_state, VS_SESSION_ACTIVATE); + + schedule_work(&session->activation_work); +} +EXPORT_SYMBOL_GPL(vs_session_handle_activate); + +static ssize_t id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vs_session_device *session = to_vs_session_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", session->session_num); +} + +/* + * The vServices session device type + */ +static ssize_t is_server_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vs_session_device *session = to_vs_session_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", session->is_server); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vs_session_device *session = to_vs_session_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", session->name); +} + +#ifdef CONFIG_VSERVICES_DEBUG +static ssize_t debug_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vs_session_device *session = to_vs_session_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%.8lx\n", session->debug_mask); +} + +static ssize_t debug_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vs_session_device *session = to_vs_session_device(dev); + int err; + + err = kstrtoul(buf, 0, &session->debug_mask); + if (err) + return err; + + /* Clear any bits we don't know about */ + session->debug_mask &= VS_DEBUG_ALL; + + return count; +} +#endif /* CONFIG_VSERVICES_DEBUG */ + +static struct device_attribute vservices_session_dev_attrs[] = { + __ATTR_RO(id), + __ATTR_RO(is_server), + __ATTR_RO(name), +#ifdef CONFIG_VSERVICES_DEBUG + __ATTR(debug_mask, S_IRUGO | S_IWUSR, + debug_mask_show, debug_mask_store), +#endif + __ATTR_NULL, +}; + +static int vs_session_free_idr(struct vs_session_device *session) +{ + mutex_lock(&vs_session_lock); + idr_remove(&session_idr, session->session_num); + mutex_unlock(&vs_session_lock); + return 0; +} + +static void vs_session_device_release(struct device *dev) +{ + struct vs_session_device *session = to_vs_session_device(dev); + + vs_session_free_idr(session); + + kfree(session->name); + kfree(session); +} + +/* + * The vServices session bus + */ +static int vs_session_bus_match(struct device *dev, + struct device_driver *driver) +{ + struct vs_session_device *session = to_vs_session_device(dev); + struct vs_session_driver *session_drv = to_vs_session_driver(driver); + + return (session->is_server == session_drv->is_server); +} + +static int vs_session_bus_remove(struct device *dev) +{ + struct vs_session_device *session = to_vs_session_device(dev); + struct vs_service_device *core_service = session->core_service; + + if (!core_service) + return 0; + + /* + * Abort any pending session activation. We rely on the transport to + * not call vs_session_handle_activate after this point. + */ + cancel_work_sync(&session->activation_work); + + /* Abort any pending fatal error handling, which is redundant now. */ + cancel_work_sync(&session->fatal_error_work); + + /* + * Delete the core service. This will implicitly delete everything + * else (in reset on the client side, and in release on the server + * side). The session holds a reference, so this won't release the + * service struct. + */ + delete_service(core_service); + + /* Now clean up the core service. */ + session->core_service = NULL; + + /* Matches the get in vs_service_register() */ + vs_put_service(core_service); + + return 0; +} + +static int vservices_session_uevent(struct device *dev, + struct kobj_uevent_env *env) +{ + struct vs_session_device *session = to_vs_session_device(dev); + + dev_dbg(dev, "uevent\n"); + + if (add_uevent_var(env, "IS_SERVER=%d", session->is_server)) + return -ENOMEM; + + if (add_uevent_var(env, "SESSION_ID=%d", session->session_num)) + return -ENOMEM; + + return 0; +} + +static void vservices_session_shutdown(struct device *dev) +{ + struct vs_session_device *session = to_vs_session_device(dev); + + dev_dbg(dev, "shutdown\n"); + + /* Do a transport reset */ + session->transport->vt->reset(session->transport); +} + +struct bus_type vs_session_bus_type = { + .name = "vservices-session", + .match = vs_session_bus_match, + .remove = vs_session_bus_remove, + .dev_attrs = vservices_session_dev_attrs, + .uevent = vservices_session_uevent, + .shutdown = vservices_session_shutdown, +}; +EXPORT_SYMBOL_GPL(vs_session_bus_type); + +/* + * Common code for the vServices client and server buses + */ +int vs_service_bus_probe(struct device *dev) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_service_driver *vsdrv = to_vs_service_driver(dev->driver); + struct vs_session_device *session = vs_service_get_session(service); + int ret; + + vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, "probe\n"); + + /* + * Increase the reference count on the service driver. We don't allow + * service driver modules to be removed if there are any device + * instances present. The devices must be explicitly removed first. + */ + if (!try_module_get(vsdrv->driver.owner)) + return -ENODEV; + + ret = vsdrv->probe(service); + if (ret) { + module_put(vsdrv->driver.owner); + return ret; + } + + service->driver_probed = true; + + try_start_service(service); + + return 0; +} +EXPORT_SYMBOL_GPL(vs_service_bus_probe); + +int vs_service_bus_remove(struct device *dev) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_service_driver *vsdrv = to_vs_service_driver(dev->driver); + + reset_service(service); + + /* Prevent reactivation of the driver */ + service->driver_probed = false; + + /* The driver has now had its reset() callback called; remove it */ + vsdrv->remove(service); + + /* + * Take the service's state mutex and spinlock. This ensures that any + * thread that is calling vs_state_lock_safe[_bh] will either complete + * now, or see the driver removal and fail, irrespective of which type + * of lock it is using. + */ + mutex_lock_nested(&service->state_mutex, service->lock_subclass); + spin_lock_bh(&service->state_spinlock); + + /* Release all the locks. */ + spin_unlock_bh(&service->state_spinlock); + mutex_unlock(&service->state_mutex); + mutex_unlock(&service->ready_lock); + +#ifdef CONFIG_VSERVICES_LOCK_DEBUG + service->state_spinlock_used = false; + service->state_mutex_used = false; +#endif + + module_put(vsdrv->driver.owner); + + return 0; +} +EXPORT_SYMBOL_GPL(vs_service_bus_remove); + +int vs_service_bus_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct vs_service_device *service = to_vs_service_device(dev); + struct vs_session_device *session = vs_service_get_session(service); + + dev_dbg(dev, "uevent\n"); + + if (add_uevent_var(env, "IS_SERVER=%d", service->is_server)) + return -ENOMEM; + + if (add_uevent_var(env, "SERVICE_ID=%d", service->id)) + return -ENOMEM; + + if (add_uevent_var(env, "SESSION_ID=%d", session->session_num)) + return -ENOMEM; + + if (add_uevent_var(env, "SERVICE_NAME=%s", service->name)) + return -ENOMEM; + + if (add_uevent_var(env, "PROTOCOL=%s", service->protocol ?: "")) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL_GPL(vs_service_bus_uevent); + +static int vs_session_create_sysfs_entry(struct vs_transport *transport, + struct vs_session_device *session, bool server, + const char *transport_name) +{ + char *sysfs_name; + struct kobject *sysfs_parent = vservices_client_root; + + if (!transport_name) + return -EINVAL; + + sysfs_name = kasprintf(GFP_KERNEL, "%s:%s", transport->type, + transport_name); + if (!sysfs_name) + return -ENOMEM; + + if (server) + sysfs_parent = vservices_server_root; + + session->sysfs_entry = kobject_create_and_add(sysfs_name, sysfs_parent); + + kfree(sysfs_name); + if (!session->sysfs_entry) + return -ENOMEM; + return 0; +} + +static int vs_session_alloc_idr(struct vs_session_device *session) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0) + int err, id; + +retry: + if (!idr_pre_get(&session_idr, GFP_KERNEL)) + return -ENOMEM; + + mutex_lock(&vs_session_lock); + err = idr_get_new_above(&session_idr, session, 0, &id); + if (err == 0) { + if (id >= VS_MAX_SESSIONS) { + /* We are out of session ids */ + idr_remove(&session_idr, id); + mutex_unlock(&vs_session_lock); + return -EBUSY; + } + } + mutex_unlock(&vs_session_lock); + if (err == -EAGAIN) + goto retry; + if (err < 0) + return err; +#else + int id; + + mutex_lock(&vs_session_lock); + id = idr_alloc(&session_idr, session, 0, VS_MAX_SESSIONS, GFP_KERNEL); + mutex_unlock(&vs_session_lock); + + if (id == -ENOSPC) + return -EBUSY; + else if (id < 0) + return id; +#endif + + session->session_num = id; + return 0; +} + +/** + * vs_session_register - register a vservices session on a transport + * @transport: vservices transport that the session will attach to + * @parent: device that implements the transport (for sysfs) + * @server: true if the session is server-side + * @transport_name: name of the transport + * + * This function is intended to be called from the probe() function of a + * transport driver. It sets up a new session device, which then either + * performs automatic service discovery (for clients) or creates sysfs nodes + * that allow the user to create services (for servers). + * + * Note that the parent is only used by the driver framework; it is not + * directly accessed by the session drivers. Thus, a single transport device + * can support multiple sessions, as long as they each have a unique struct + * vs_transport. + * + * Note: This function may sleep, and therefore must not be called from + * interrupt context. + * + * Returns a pointer to the new device, or an error pointer. + */ +struct vs_session_device *vs_session_register(struct vs_transport *transport, + struct device *parent, bool server, const char *transport_name) +{ + struct device *dev; + struct vs_session_device *session; + int ret = -ENOMEM; + + WARN_ON(!transport); + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + goto fail_session_alloc; + + session->transport = transport; + session->is_server = server; + session->name = kstrdup(transport_name, GFP_KERNEL); + if (!session->name) + goto fail_free_session; + + INIT_WORK(&session->activation_work, session_activation_work); + INIT_WORK(&session->fatal_error_work, session_fatal_error_work); + +#ifdef CONFIG_VSERVICES_DEBUG + session->debug_mask = default_debug_mask & VS_DEBUG_ALL; +#endif + + idr_init(&session->service_idr); + mutex_init(&session->service_idr_lock); + + /* + * We must create session sysfs entry before device_create + * so, that sysfs entry is available while registering + * core service. + */ + ret = vs_session_create_sysfs_entry(transport, session, server, + transport_name); + if (ret) + goto fail_free_session; + + ret = vs_session_alloc_idr(session); + if (ret) + goto fail_sysfs_entry; + + dev = &session->dev; + dev->parent = parent; + dev->bus = &vs_session_bus_type; + dev->release = vs_session_device_release; + dev_set_name(dev, "vservice:%d", session->session_num); + + ret = device_register(dev); + if (ret) { + goto fail_session_map; + } + + /* Add a symlink to transport device inside session device sysfs dir */ + if (parent) { + ret = sysfs_create_link(&session->dev.kobj, + &parent->kobj, VS_TRANSPORT_SYMLINK_NAME); + if (ret) { + dev_err(&session->dev, + "Error %d creating transport symlink\n", + ret); + goto fail_session_device_unregister; + } + } + + return session; + +fail_session_device_unregister: + device_unregister(&session->dev); + kobject_put(session->sysfs_entry); + /* Remaining cleanup will be done in vs_session_release */ + return ERR_PTR(ret); +fail_session_map: + vs_session_free_idr(session); +fail_sysfs_entry: + kobject_put(session->sysfs_entry); +fail_free_session: + kfree(session->name); + kfree(session); +fail_session_alloc: + return ERR_PTR(ret); +} +EXPORT_SYMBOL(vs_session_register); + +void vs_session_start(struct vs_session_device *session) +{ + struct vs_service_device *core_service = session->core_service; + + if (WARN_ON(!core_service)) + return; + + blocking_notifier_call_chain(&vs_session_notifier_list, + VS_SESSION_NOTIFY_ADD, session); + + vs_service_start(core_service); +} +EXPORT_SYMBOL_GPL(vs_session_start); + +/** + * vs_session_unregister - unregister a session device + * @session: the session device to unregister + */ +void vs_session_unregister(struct vs_session_device *session) +{ + if (session->dev.parent) + sysfs_remove_link(&session->dev.kobj, VS_TRANSPORT_SYMLINK_NAME); + blocking_notifier_call_chain(&vs_session_notifier_list, + VS_SESSION_NOTIFY_REMOVE, session); + + device_unregister(&session->dev); + + kobject_put(session->sysfs_entry); +} +EXPORT_SYMBOL_GPL(vs_session_unregister); + +struct service_unbind_work_struct { + struct vs_service_device *service; + struct work_struct work; +}; + +static void service_unbind_work(struct work_struct *work) +{ + struct service_unbind_work_struct *unbind_work = container_of(work, + struct service_unbind_work_struct, work); + + device_release_driver(&unbind_work->service->dev); + + /* Matches vs_get_service() in vs_session_unbind_driver() */ + vs_put_service(unbind_work->service); + kfree(unbind_work); +} + +int vs_session_unbind_driver(struct vs_service_device *service) +{ + struct service_unbind_work_struct *unbind_work = + kmalloc(sizeof(*unbind_work), GFP_KERNEL); + + if (!unbind_work) + return -ENOMEM; + + INIT_WORK(&unbind_work->work, service_unbind_work); + + /* Put in service_unbind_work() */ + unbind_work->service = vs_get_service(service); + schedule_work(&unbind_work->work); + + return 0; +} +EXPORT_SYMBOL_GPL(vs_session_unbind_driver); + +static int __init vservices_init(void) +{ + int r; + + printk(KERN_INFO "vServices Framework 1.0\n"); + + vservices_root = kobject_create_and_add("vservices", NULL); + if (!vservices_root) { + r = -ENOMEM; + goto fail_create_root; + } + + r = bus_register(&vs_session_bus_type); + if (r < 0) + goto fail_bus_register; + + r = vs_devio_init(); + if (r < 0) + goto fail_devio_init; + + return 0; + +fail_devio_init: + bus_unregister(&vs_session_bus_type); +fail_bus_register: + kobject_put(vservices_root); +fail_create_root: + return r; +} + +static void __exit vservices_exit(void) +{ + printk(KERN_INFO "vServices Framework exit\n"); + + vs_devio_exit(); + bus_unregister(&vs_session_bus_type); + kobject_put(vservices_root); +} + +subsys_initcall(vservices_init); +module_exit(vservices_exit); + +MODULE_DESCRIPTION("OKL4 Virtual Services Session"); +MODULE_AUTHOR("Open Kernel Labs, Inc"); diff --git a/drivers/vservices/session.h b/drivers/vservices/session.h new file mode 100644 index 000000000000..f51d535b3576 --- /dev/null +++ b/drivers/vservices/session.h @@ -0,0 +1,173 @@ +/* + * drivers/vservices/session.h + * + * 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. + * + * Definitions related to the vservices session bus and its client and server + * session drivers. The interfaces in this file are implementation details of + * the vServices framework and should not be used by transport or service + * drivers. + */ + +#ifndef _VSERVICES_SESSION_PRIV_H_ +#define _VSERVICES_SESSION_PRIV_H_ + +/* Maximum number of sessions allowed */ +#define VS_MAX_SESSIONS 64 + +#include "debug.h" + +/* For use by the core server */ +#define VS_SERVICE_AUTO_ALLOCATE_ID 0xffff +#define VS_SERVICE_ALREADY_RESET 1 + +/* + * The upper bits of the service id are reserved for transport driver specific + * use. The reserve bits are always zeroed out above the transport layer. + */ +#define VS_SERVICE_ID_TRANSPORT_BITS 4 +#define VS_SERVICE_ID_TRANSPORT_OFFSET 12 +#define VS_SERVICE_ID_TRANSPORT_MASK ((1 << VS_SERVICE_ID_TRANSPORT_BITS) - 1) +#define VS_SERVICE_ID_MASK \ + (~(VS_SERVICE_ID_TRANSPORT_MASK << VS_SERVICE_ID_TRANSPORT_OFFSET)) + +/* Number of bits needed to represent the service id range as a bitmap. */ +#define VS_SERVICE_ID_BITMAP_BITS \ + (1 << ((sizeof(vs_service_id_t) * 8) - VS_SERVICE_ID_TRANSPORT_BITS)) + +/* High service ids are reserved for use by the transport drivers */ +#define VS_SERVICE_ID_RESERVED(x) \ + ((1 << VS_SERVICE_ID_TRANSPORT_OFFSET) - (x)) + +#define VS_SERVICE_ID_RESERVED_1 VS_SERVICE_ID_RESERVED(1) + +/* Name of the session device symlink in service device sysfs directory */ +#define VS_SESSION_SYMLINK_NAME "session" + +/* Name of the transport device symlink in session device sysfs directory */ +#define VS_TRANSPORT_SYMLINK_NAME "transport" + +static inline unsigned int +vs_get_service_id_reserved_bits(vs_service_id_t service_id) +{ + return (service_id >> VS_SERVICE_ID_TRANSPORT_OFFSET) & + VS_SERVICE_ID_TRANSPORT_MASK; +} + +static inline vs_service_id_t vs_get_real_service_id(vs_service_id_t service_id) +{ + return service_id & VS_SERVICE_ID_MASK; +} + +static inline void vs_set_service_id_reserved_bits(vs_service_id_t *service_id, + unsigned int reserved_bits) +{ + *service_id &= ~(VS_SERVICE_ID_TRANSPORT_MASK << + VS_SERVICE_ID_TRANSPORT_OFFSET); + *service_id |= (reserved_bits & VS_SERVICE_ID_TRANSPORT_MASK) << + VS_SERVICE_ID_TRANSPORT_OFFSET; +} + +extern struct bus_type vs_session_bus_type; +extern struct kobject *vservices_root; +extern struct kobject *vservices_server_root; +extern struct kobject *vservices_client_root; + +/** + * struct vs_session_driver - Session driver + * @driver: Linux device model driver structure + * @service_bus: Pointer to either the server or client bus type + * @is_server: True if this driver is for a server session, false if it is for + * a client session + * @service_added: Called when a non-core service is added. + * @service_start: Called when a non-core service is started. + * @service_local_reset: Called when an active non-core service driver becomes + * inactive. + * @service_removed: Called when a non-core service is removed. + */ +struct vs_session_driver { + struct device_driver driver; + struct bus_type *service_bus; + bool is_server; + + /* These are all called with the core service state lock held. */ + int (*service_added)(struct vs_session_device *session, + struct vs_service_device *service); + int (*service_start)(struct vs_session_device *session, + struct vs_service_device *service); + int (*service_local_reset)(struct vs_session_device *session, + struct vs_service_device *service); + int (*service_removed)(struct vs_session_device *session, + struct vs_service_device *service); +}; + +#define to_vs_session_driver(drv) \ + container_of(drv, struct vs_session_driver, driver) + +/* Service lookup */ +extern struct vs_service_device * vs_session_get_service( + struct vs_session_device *session, + vs_service_id_t service_id); + +/* Service creation & destruction */ +extern struct vs_service_device * +vs_service_register(struct vs_session_device *session, + struct vs_service_device *parent, + vs_service_id_t service_id, + const char *protocol, + const char *name, + const void *plat_data); + +extern bool vs_service_start(struct vs_service_device *service); + +extern int vs_service_delete(struct vs_service_device *service, + struct vs_service_device *caller); + +extern int vs_service_handle_delete(struct vs_service_device *service); + +/* Service reset handling */ +extern int vs_service_handle_reset(struct vs_session_device *session, + vs_service_id_t service_id, bool disable); +extern int vs_service_enable(struct vs_service_device *service); + +extern void vs_session_enable_noncore(struct vs_session_device *session); +extern void vs_session_disable_noncore(struct vs_session_device *session); +extern void vs_session_delete_noncore(struct vs_session_device *session); + +/* Service bus driver management */ +extern int vs_service_bus_probe(struct device *dev); +extern int vs_service_bus_remove(struct device *dev); +extern int vs_service_bus_uevent(struct device *dev, + struct kobj_uevent_env *env); + +#ifdef CONFIG_VSERVICES_CHAR_DEV + +extern int vs_devio_init(void); +extern void vs_devio_exit(void); + +extern struct vs_service_device *vs_service_lookup_by_devt(dev_t dev); + +extern struct vs_service_driver vs_devio_server_driver; +extern struct vs_service_driver vs_devio_client_driver; + +extern int vservices_cdev_major; + +#else /* !CONFIG_VSERVICES_CHAR_DEV */ + +static inline int vs_devio_init(void) +{ + return 0; +} + +static inline void vs_devio_exit(void) +{ +} + +#endif /* !CONFIG_VSERVICES_CHAR_DEV */ + +#endif /* _VSERVICES_SESSION_PRIV_H_ */ diff --git a/drivers/vservices/skeleton_driver.c b/drivers/vservices/skeleton_driver.c new file mode 100644 index 000000000000..cfbc5dfe174f --- /dev/null +++ b/drivers/vservices/skeleton_driver.c @@ -0,0 +1,133 @@ +/* + * drivers/vservices/skeleton_driver.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. + * + * Skeleton testing driver for templating vService client/server drivers + */ + +#include +#include +#include + +#include +#include +#include + +struct skeleton_info { + unsigned dummy; +}; + +static void vs_skeleton_handle_start(struct vs_service_device *service) +{ + /* NOTE: Do not change this message - is it used for system testing */ + dev_info(&service->dev, "skeleton handle_start\n"); +} + +static int vs_skeleton_handle_message(struct vs_service_device *service, + struct vs_mbuf *mbuf) +{ + dev_info(&service->dev, "skeleton handle_messasge\n"); + return -EBADMSG; +} + +static void vs_skeleton_handle_notify(struct vs_service_device *service, + u32 flags) +{ + dev_info(&service->dev, "skeleton handle_notify\n"); +} + +static void vs_skeleton_handle_reset(struct vs_service_device *service) +{ + dev_info(&service->dev, "skeleton handle_reset %s service %d\n", + service->is_server ? "server" : "client", service->id); +} + +static int vs_skeleton_probe(struct vs_service_device *service) +{ + struct skeleton_info *info; + int err = -ENOMEM; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + goto fail; + + dev_set_drvdata(&service->dev, info); + return 0; + +fail: + return err; +} + +static int vs_skeleton_remove(struct vs_service_device *service) +{ + struct skeleton_info *info = dev_get_drvdata(&service->dev); + + dev_info(&service->dev, "skeleton remove\n"); + kfree(info); + return 0; +} + +static struct vs_service_driver server_skeleton_driver = { + .protocol = "com.ok-labs.skeleton", + .is_server = true, + .probe = vs_skeleton_probe, + .remove = vs_skeleton_remove, + .start = vs_skeleton_handle_start, + .receive = vs_skeleton_handle_message, + .notify = vs_skeleton_handle_notify, + .reset = vs_skeleton_handle_reset, + .driver = { + .name = "vs-server-skeleton", + .owner = THIS_MODULE, + .bus = &vs_server_bus_type, + }, +}; + +static struct vs_service_driver client_skeleton_driver = { + .protocol = "com.ok-labs.skeleton", + .is_server = false, + .probe = vs_skeleton_probe, + .remove = vs_skeleton_remove, + .start = vs_skeleton_handle_start, + .receive = vs_skeleton_handle_message, + .notify = vs_skeleton_handle_notify, + .reset = vs_skeleton_handle_reset, + .driver = { + .name = "vs-client-skeleton", + .owner = THIS_MODULE, + .bus = &vs_client_bus_type, + }, +}; + +static int __init vs_skeleton_init(void) +{ + int ret; + + ret = driver_register(&server_skeleton_driver.driver); + if (ret) + return ret; + + ret = driver_register(&client_skeleton_driver.driver); + if (ret) + driver_unregister(&server_skeleton_driver.driver); + + return ret; +} + +static void __exit vs_skeleton_exit(void) +{ + driver_unregister(&server_skeleton_driver.driver); + driver_unregister(&client_skeleton_driver.driver); +} + +module_init(vs_skeleton_init); +module_exit(vs_skeleton_exit); + +MODULE_DESCRIPTION("OKL4 Virtual Services Skeleton Client/Server Driver"); +MODULE_AUTHOR("Open Kernel Labs, Inc"); diff --git a/drivers/vservices/transport.h b/drivers/vservices/transport.h new file mode 100644 index 000000000000..8e5055ca2269 --- /dev/null +++ b/drivers/vservices/transport.h @@ -0,0 +1,40 @@ +/* + * include/vservices/transport.h + * + * 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. + * + * This file defines the private interface that vServices transport drivers + * must provide to the vservices session and protocol layers. The transport, + * transport vtable, and message buffer structures are defined in the public + * header. + */ + +#ifndef _VSERVICES_TRANSPORT_PRIV_H_ +#define _VSERVICES_TRANSPORT_PRIV_H_ + +#include +#include + +#include +#include +#include + +/** + * struct vs_notify_info - Notification information stored in the transport + * @service_id: Service id for this notification info + * @offset: Offset into the notification mapping + */ +struct vs_notify_info { + vs_service_id_t service_id; + unsigned offset; +}; + +#define VS_MAX_SERVICES 128 +#define VS_MAX_SERVICE_ID (VS_MAX_SERVICES - 1) + +#endif /* _VSERVICES_TRANSPORT_PRIV_H_ */ diff --git a/drivers/vservices/transport/Kconfig b/drivers/vservices/transport/Kconfig new file mode 100644 index 000000000000..cd1c97cddab1 --- /dev/null +++ b/drivers/vservices/transport/Kconfig @@ -0,0 +1,7 @@ +# +# vServices Transport driver configuration +# + +menu "Transport drivers" + +endmenu diff --git a/drivers/vservices/transport/Makefile b/drivers/vservices/transport/Makefile new file mode 100644 index 000000000000..ae1c943993ac --- /dev/null +++ b/drivers/vservices/transport/Makefile @@ -0,0 +1,2 @@ +ccflags-y += -Werror +ccflags-$(CONFIG_VSERVICES_DEBUG) += -DDEBUG diff --git a/include/Kbuild b/include/Kbuild new file mode 100644 index 000000000000..9205b04e5087 --- /dev/null +++ b/include/Kbuild @@ -0,0 +1,6 @@ +# Top-level Makefile calls into asm-$(ARCH) +# List only non-arch directories below + +ifneq ($(VSERVICES_SUPPORT), "") +header-y += vservices/ +endif diff --git a/include/linux/Kbuild.vservices b/include/linux/Kbuild.vservices new file mode 100644 index 000000000000..392f559f9fde --- /dev/null +++ b/include/linux/Kbuild.vservices @@ -0,0 +1,3 @@ +# +# Virtual Services headers which need to be exported for user-space +# diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index ca2787d9bf0f..6527884402d5 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -11,3 +11,7 @@ endif ifeq ($(wildcard $(srctree)/arch/$(SRCARCH)/include/uapi/asm/kvm_para.h),) no-export-headers += kvm_para.h endif + +ifneq ($(VSERVICES_SUPPORT), "") +include include/linux/Kbuild.vservices +endif diff --git a/include/vservices/Kbuild b/include/vservices/Kbuild new file mode 100644 index 000000000000..8b955fc84ef1 --- /dev/null +++ b/include/vservices/Kbuild @@ -0,0 +1,2 @@ +header-y += protocol/ +header-y += ioctl.h diff --git a/include/vservices/buffer.h b/include/vservices/buffer.h new file mode 100644 index 000000000000..910aa07769f2 --- /dev/null +++ b/include/vservices/buffer.h @@ -0,0 +1,239 @@ +/* + * include/vservices/buffer.h + * + * 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. + * + * This file defines simple wrapper types for strings and variable-size buffers + * that are stored inside Virtual Services message buffers. + */ + +#ifndef _VSERVICES_BUFFER_H_ +#define _VSERVICES_BUFFER_H_ + +#include +#include +#include + +struct vs_mbuf; + +/** + * struct vs_string - Virtual Services fixed sized string type + * @ptr: String pointer + * @max_size: Maximum length of the string in bytes + * + * A handle to a possibly NUL-terminated string stored in a message buffer. If + * the size of the string equals to max_size, the string is not NUL-terminated. + * If the protocol does not specify an encoding, the encoding is assumed to be + * UTF-8. Wide character encodings are not supported by this type; use struct + * vs_pbuf for wide character strings. + */ +struct vs_string { + char *ptr; + size_t max_size; +}; + +/** + * vs_string_copyout - Copy a Virtual Services string to a C string buffer. + * @dest: C string to copy to + * @src: Virtual Services string to copy from + * @max_size: Size of the destination buffer, including the NUL terminator. + * + * The behaviour is similar to strlcpy(): that is, the copied string + * is guaranteed not to exceed the specified size (including the NUL + * terminator byte), and is guaranteed to be NUL-terminated as long as + * the size is nonzero (unlike strncpy()). + * + * The return value is the size of the input string (even if the output was + * truncated); this is to make truncation easy to detect. + */ +static inline size_t +vs_string_copyout(char *dest, const struct vs_string *src, size_t max_size) +{ + size_t src_len = strnlen(src->ptr, src->max_size); + + if (max_size) { + size_t dest_len = min(src_len, max_size - 1); + + memcpy(dest, src->ptr, dest_len); + dest[dest_len] = '\0'; + } + return src_len; +} + +/** + * vs_string_copyin_len - Copy a C string, up to a given length, into a Virtual + * Services string. + * @dest: Virtual Services string to copy to + * @src: C string to copy from + * @max_size: Maximum number of bytes to copy + * + * Returns the number of bytes copied, which may be less than the input + * string's length. + */ +static inline size_t +vs_string_copyin_len(struct vs_string *dest, const char *src, size_t max_size) +{ + strncpy(dest->ptr, src, min(max_size, dest->max_size)); + + return strnlen(dest->ptr, dest->max_size); +} + +/** + * vs_string_copyin - Copy a C string into a Virtual Services string. + * @dest: Virtual Services string to copy to + * @src: C string to copy from + * + * Returns the number of bytes copied, which may be less than the input + * string's length. + */ +static inline size_t +vs_string_copyin(struct vs_string *dest, const char *src) +{ + return vs_string_copyin_len(dest, src, dest->max_size); +} + +/** + * vs_string_length - Return the size of the string stored in a Virtual Services + * string. + * @str: Virtual Service string to get the length of + */ +static inline size_t +vs_string_length(struct vs_string *str) +{ + return strnlen(str->ptr, str->max_size); +} + +/** + * vs_string_dup - Allocate a C string buffer and copy a Virtual Services string + * into it. + * @str: Virtual Services string to duplicate + */ +static inline char * +vs_string_dup(struct vs_string *str, gfp_t gfp) +{ + size_t len; + char *ret; + + len = strnlen(str->ptr, str->max_size) + 1; + ret = kmalloc(len, gfp); + if (ret) + vs_string_copyout(ret, str, len); + return ret; +} + +/** + * vs_string_max_size - Return the maximum size of a Virtual Services string, + * not including the NUL terminator if the lenght of the + * string is equal to max_size. + * + * @str Virtual Services string to return the maximum size of. + * + * @return The maximum size of the string. + */ +static inline size_t +vs_string_max_size(struct vs_string *str) +{ + return str->max_size; +} + +/** + * struct vs_pbuf - Handle to a variable-size buffered payload. + * @data: Data buffer + * @size: Current size of the buffer + * @max_size: Maximum size of the buffer + * + * This is similar to struct vs_string, except that has an explicitly + * stored size rather than being null-terminated. The functions that + * return ssize_t all return the new size of the modified buffer, and + * will return a negative size if the buffer overflows. + */ +struct vs_pbuf { + void *data; + size_t size, max_size; +}; + +/** + * vs_pbuf_size - Get the size of a pbuf + * @pbuf: pbuf to get the size of + */ +static inline size_t vs_pbuf_size(const struct vs_pbuf *pbuf) +{ + return pbuf->size; +} + +/** + * vs_pbuf_data - Get the data pointer for a a pbuf + * @pbuf: pbuf to get the data pointer for + */ +static inline const void *vs_pbuf_data(const struct vs_pbuf *pbuf) +{ + return pbuf->data; +} + +/** + * vs_pbuf_resize - Resize a pbuf + * @pbuf: pbuf to resize + * @size: New size + */ +static inline ssize_t vs_pbuf_resize(struct vs_pbuf *pbuf, size_t size) +{ + if (size > pbuf->max_size) + return -EOVERFLOW; + + pbuf->size = size; + return size; +} + +/** + * vs_pbuf_copyin - Copy data into a pbuf + * @pbuf: pbuf to copy data into + * @offset: Offset to copy data to + * @data: Pointer to data to copy into the pbuf + * @nbytes: Number of bytes to copy into the pbuf + */ +static inline ssize_t vs_pbuf_copyin(struct vs_pbuf *pbuf, off_t offset, + const void *data, size_t nbytes) +{ + if (offset + nbytes > pbuf->size) + return -EOVERFLOW; + + memcpy(pbuf->data + offset, data, nbytes); + + return nbytes; +} + +/** + * vs_pbuf_append - Append data to a pbuf + * @pbuf: pbuf to append to + * @data: Pointer to data to append to the pbuf + * @nbytes: Number of bytes to append + */ +static inline ssize_t vs_pbuf_append(struct vs_pbuf *pbuf, + const void *data, size_t nbytes) +{ + if (pbuf->size + nbytes > pbuf->max_size) + return -EOVERFLOW; + + memcpy(pbuf->data + pbuf->size, data, nbytes); + pbuf->size += nbytes; + + return pbuf->size; +} + +/** + * vs_pbuf_dup_string - Duplicate the contents of a pbuf as a C string. The + * string is allocated and must be freed using kfree. + * @pbuf: pbuf to convert + * @gfp_flags: GFP flags for the string allocation + */ +static inline char *vs_pbuf_dup_string(struct vs_pbuf *pbuf, gfp_t gfp_flags) +{ + return kstrndup(pbuf->data, pbuf->size, gfp_flags); +} + +#endif /* _VSERVICES_BUFFER_H_ */ diff --git a/include/vservices/ioctl.h b/include/vservices/ioctl.h new file mode 100644 index 000000000000..d96fcabb9829 --- /dev/null +++ b/include/vservices/ioctl.h @@ -0,0 +1,48 @@ +/* + * vservices/ioctl.h - Interface to service character devices + * + * Copyright (c) 2016, Cog Systems Pty Ltd + * + * 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. + */ + +#ifndef __LINUX_PUBLIC_VSERVICES_IOCTL_H__ +#define __LINUX_PUBLIC_VSERVICES_IOCTL_H__ + +#include +#include + +/* ioctls that work on any opened service device */ +#define IOCTL_VS_RESET_SERVICE _IO('4', 0) +#define IOCTL_VS_GET_NAME _IOR('4', 1, char[16]) +#define IOCTL_VS_GET_PROTOCOL _IOR('4', 2, char[32]) + +/* + * Claim a device for user I/O (if no kernel driver is attached). The claim + * persists until the char device is closed. + */ +struct vs_ioctl_bind { + __u32 send_quota; + __u32 recv_quota; + __u32 send_notify_bits; + __u32 recv_notify_bits; + size_t msg_size; +}; +#define IOCTL_VS_BIND_CLIENT _IOR('4', 3, struct vs_ioctl_bind) +#define IOCTL_VS_BIND_SERVER _IOWR('4', 4, struct vs_ioctl_bind) + +/* send and receive messages and notifications */ +#define IOCTL_VS_NOTIFY _IOW('4', 5, __u32) +struct vs_ioctl_iovec { + union { + __u32 iovcnt; /* input */ + __u32 notify_bits; /* output (recv only) */ + }; + struct iovec *iov; +}; +#define IOCTL_VS_SEND _IOW('4', 6, struct vs_ioctl_iovec) +#define IOCTL_VS_RECV _IOWR('4', 7, struct vs_ioctl_iovec) + +#endif /* __LINUX_PUBLIC_VSERVICES_IOCTL_H__ */ diff --git a/include/vservices/protocol/Kbuild b/include/vservices/protocol/Kbuild new file mode 100644 index 000000000000..374d9b69a5df --- /dev/null +++ b/include/vservices/protocol/Kbuild @@ -0,0 +1,12 @@ +# +# Find all of the protocol directory names, and get the basename followed +# by a trailing slash. +# +protocols=$(shell find include/vservices/protocol/ -mindepth 1 -type d -exec basename {} \;) +protocol_dirs=$(foreach p, $(protocols), $(p)/) + +# +# Export the headers for all protocols. The kbuild file in each protocol +# directory specifies exactly which headers to export. +# +header-y += $(protocol_dirs) diff --git a/include/vservices/protocol/core.h b/include/vservices/protocol/core.h new file mode 100644 index 000000000000..3a86af5a5ec8 --- /dev/null +++ b/include/vservices/protocol/core.h @@ -0,0 +1,145 @@ +/* + * include/vservices/protocol/core.h + * + * 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. + * + * These are the common generated definitions for the core protocol drivers; + * specifically the message IDs and the protocol state representation. + * + * This is currently hand-generated, but will eventually be autogenerated, + * from the protocol specifications in core.vs. Please keep it consistent + * with that file. + */ + +#define VSERVICE_CORE_PROTOCOL_NAME "com.ok-labs.core" +#define VSERVICE_CORE_PARAM_SIZE_SERVICE_INFO__PROTOCOL_NAME 32 +#define VSERVICE_CORE_PARAM_SIZE_SERVICE_INFO__SERVICE_NAME 16 + +/* + * Identifiers for in-band messages. + * + * This definition applies in both directions, because there is no practical + * limit on message IDs (services are unlikely to define 2^16 distinct message + * names). + */ +typedef enum { + /** simple_protocol core **/ + /* message out startup */ + VSERVICE_CORE_MSG_STARTUP, + + /* message out shutdown */ + VSERVICE_CORE_MSG_SHUTDOWN, + + /* command in sync connect */ + VSERVICE_CORE_REQ_CONNECT, + VSERVICE_CORE_ACK_CONNECT, + VSERVICE_CORE_NACK_CONNECT, + + /* command in sync disconnect */ + VSERVICE_CORE_REQ_DISCONNECT, + VSERVICE_CORE_ACK_DISCONNECT, + VSERVICE_CORE_NACK_DISCONNECT, + + /* command in service_count */ + VSERVICE_CORE_REQ_SERVICE_COUNT, + VSERVICE_CORE_ACK_SERVICE_COUNT, + VSERVICE_CORE_NACK_SERVICE_COUNT, + + /* command in queued service_info */ + VSERVICE_CORE_REQ_SERVICE_INFO, + VSERVICE_CORE_ACK_SERVICE_INFO, + VSERVICE_CORE_NACK_SERVICE_INFO, + + /* message inout service_reset */ + VSERVICE_CORE_MSG_SERVICE_RESET, + + /* message inout service_ready */ + VSERVICE_CORE_MSG_SERVICE_READY, + + /* message out notification bits */ + VSERVICE_CORE_MSG_NOTIFICATION_BITS_INFO, + +} vservice_core_message_id_t; + +/* + * Notification bits are defined separately for each direction because there + * is relatively limited space to allocate them from (specifically, the bits in + * a machine word). It is unlikely but possible for a protocol to reach this + * limit. + */ + +/* Bits in the in (client -> server) notification bitmask. */ +typedef enum { + /** simple_protocol core **/ + /* No in notifications */ + + VSERVICE_CORE_NBIT_IN__COUNT = 0, +} vservice_core_nbit_in_t; + +/* Masks for the in notification bits */ +/* No in notifications */ + +/* Bits in the out (server -> client) notification bitmask. */ +typedef enum { + /** simple_protocol core **/ + /* notification out reenumerate */ + VSERVICE_CORE_NBIT_OUT_REENUMERATE = 0, + + VSERVICE_CORE_NBIT_OUT__COUNT, +} vservice_core_nbit_out_t; + +/* Masks for the out notification bits */ +#define VSERVICE_CORE_NMASK_OUT_REENUMERATE \ + (1 << VSERVICE_CORE_NBIT_OUT_REENUMERATE) + +/* Valid states of the interface's generated state machine. */ +typedef enum { + /* state offline */ + VSERVICE_CORE_STATE_OFFLINE = 0, + + /* state disconnected */ + VSERVICE_CORE_STATE_DISCONNECTED, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT, + + /* state connected */ + VSERVICE_CORE_STATE_CONNECTED, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT, + + /* reset offline */ + VSERVICE_CORE_STATE__RESET = VSERVICE_CORE_STATE_OFFLINE, +} vservice_core_statenum_t; + +typedef struct { + vservice_core_statenum_t statenum; + bool pending_service_count; + unsigned pending_service_info; +} vservice_core_state_t; + +#define VSERVICE_CORE_RESET_STATE (vservice_core_state_t) { \ + .statenum = VSERVICE_CORE_STATE__RESET, \ + .pending_service_count = false, \ + .pending_service_info = 0 } + +#define VSERVICE_CORE_STATE_IS_OFFLINE(state) ( \ + ((state).statenum == VSERVICE_CORE_STATE_OFFLINE)) +#define VSERVICE_CORE_STATE_IS_DISCONNECTED(state) ( \ + ((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED) || \ + ((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED__CONNECT)) +#define VSERVICE_CORE_STATE_IS_CONNECTED(state) ( \ + ((state).statenum == VSERVICE_CORE_STATE_CONNECTED) || \ + ((state).statenum == VSERVICE_CORE_STATE_CONNECTED__DISCONNECT)) + +#define VSERVICE_CORE_STATE_VALID(state) \ + VSERVICE_CORE_STATE_IS_OFFLINE(state) ? ( \ + ((state).pending_service_count == false) && \ + ((state).pending_service_info == 0)) : \ + VSERVICE_CORE_STATE_IS_DISCONNECTED(state) ? ( \ + ((state).pending_service_count == false) && \ + ((state).pending_service_info == 0)) : \ + VSERVICE_CORE_STATE_IS_CONNECTED(state) ? true : \ + false) diff --git a/include/vservices/protocol/core/Kbuild b/include/vservices/protocol/core/Kbuild new file mode 100644 index 000000000000..ec3cbe813b00 --- /dev/null +++ b/include/vservices/protocol/core/Kbuild @@ -0,0 +1 @@ +header-y += types.h diff --git a/include/vservices/protocol/core/client.h b/include/vservices/protocol/core/client.h new file mode 100644 index 000000000000..3d529990ad19 --- /dev/null +++ b/include/vservices/protocol/core/client.h @@ -0,0 +1,155 @@ + +/* + * 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. + */ + +#if !defined(__VSERVICES_CLIENT_CORE__) +#define __VSERVICES_CLIENT_CORE__ + +struct vs_service_device; +struct vs_client_core_state; + +struct vs_client_core { + + /* + * If set to false then the receive message handlers are run from + * workqueue context and are allowed to sleep. If set to true the + * message handlers are run from tasklet context and may not sleep. + */ + bool rx_atomic; + + /* + * If this is set to true along with rx_atomic, the driver is allowed + * to send messages from softirq contexts other than the receive + * message handlers, after calling vs_service_state_lock_bh. Otherwise, + * messages may only be sent from the receive message handlers, or + * from task context after calling vs_service_state_lock. This must + * not be set to true if rx_atomic is set to false. + */ + bool tx_atomic; + /** session setup **/ + struct vs_client_core_state *(*alloc) (struct vs_service_device * + service); + void (*release) (struct vs_client_core_state * _state); + + struct vs_service_driver *driver; + + /** Core service base interface **/ + void (*start) (struct vs_client_core_state * _state); + void (*reset) (struct vs_client_core_state * _state); + /** Send/receive state callbacks **/ + int (*tx_ready) (struct vs_client_core_state * _state); + + struct { + int (*state_change) (struct vs_client_core_state * _state, + vservice_core_statenum_t old, + vservice_core_statenum_t new); + + int (*ack_connect) (struct vs_client_core_state * _state); + int (*nack_connect) (struct vs_client_core_state * _state); + + int (*ack_disconnect) (struct vs_client_core_state * _state); + int (*nack_disconnect) (struct vs_client_core_state * _state); + + int (*msg_startup) (struct vs_client_core_state * _state, + uint32_t core_in_quota, + uint32_t core_out_quota); + + int (*msg_shutdown) (struct vs_client_core_state * _state); + + int (*msg_service_created) (struct vs_client_core_state * + _state, uint32_t service_id, + struct vs_string service_name, + struct vs_string protocol_name, + struct vs_mbuf * _mbuf); + + int (*msg_service_removed) (struct vs_client_core_state * + _state, uint32_t service_id); + + int (*msg_server_ready) (struct vs_client_core_state * _state, + uint32_t service_id, uint32_t in_quota, + uint32_t out_quota, + uint32_t in_bit_offset, + uint32_t in_num_bits, + uint32_t out_bit_offset, + uint32_t out_num_bits); + + int (*msg_service_reset) (struct vs_client_core_state * _state, + uint32_t service_id); + + } core; +}; + +struct vs_client_core_state { + vservice_core_protocol_state_t state; + struct vs_service_device *service; + bool released; +}; + +extern int vs_client_core_reopen(struct vs_client_core_state *_state); + +extern int vs_client_core_close(struct vs_client_core_state *_state); + + /** interface core **/ +/* command sync connect */ +extern int vs_client_core_core_req_connect(struct vs_client_core_state *_state, + gfp_t flags); + + /* command sync disconnect */ +extern int vs_client_core_core_req_disconnect(struct vs_client_core_state + *_state, gfp_t flags); + + /* message startup */ +/* message shutdown */ +/* message service_created */ +extern int vs_client_core_core_getbufs_service_created(struct + vs_client_core_state + *_state, + struct vs_string + *service_name, + struct vs_string + *protocol_name, + struct vs_mbuf *_mbuf); +extern int vs_client_core_core_free_service_created(struct vs_client_core_state + *_state, + struct vs_string + *service_name, + struct vs_string + *protocol_name, + struct vs_mbuf *_mbuf); + /* message service_removed */ +/* message server_ready */ +/* message service_reset */ +extern int vs_client_core_core_send_service_reset(struct vs_client_core_state + *_state, uint32_t service_id, + gfp_t flags); + +/** Module registration **/ + +struct module; + +extern int __vservice_core_client_register(struct vs_client_core *client, + const char *name, + struct module *owner); + +static inline int vservice_core_client_register(struct vs_client_core *client, + const char *name) +{ +#ifdef MODULE + extern struct module __this_module; + struct module *this_module = &__this_module; +#else + struct module *this_module = NULL; +#endif + + return __vservice_core_client_register(client, name, this_module); +} + +extern int vservice_core_client_unregister(struct vs_client_core *client); + +#endif /* ! __VSERVICES_CLIENT_CORE__ */ diff --git a/include/vservices/protocol/core/common.h b/include/vservices/protocol/core/common.h new file mode 100644 index 000000000000..b496416119c6 --- /dev/null +++ b/include/vservices/protocol/core/common.h @@ -0,0 +1,38 @@ + +/* + * 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. + */ + +#if !defined(__VSERVICES_CORE_PROTOCOL_H__) +#define __VSERVICES_CORE_PROTOCOL_H__ + +#define VSERVICE_CORE_PROTOCOL_NAME "com.ok-labs.core" +typedef enum { + VSERVICE_CORE_CORE_REQ_CONNECT, + VSERVICE_CORE_CORE_ACK_CONNECT, + VSERVICE_CORE_CORE_NACK_CONNECT, + VSERVICE_CORE_CORE_REQ_DISCONNECT, + VSERVICE_CORE_CORE_ACK_DISCONNECT, + VSERVICE_CORE_CORE_NACK_DISCONNECT, + VSERVICE_CORE_CORE_MSG_STARTUP, + VSERVICE_CORE_CORE_MSG_SHUTDOWN, + VSERVICE_CORE_CORE_MSG_SERVICE_CREATED, + VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED, + VSERVICE_CORE_CORE_MSG_SERVER_READY, + VSERVICE_CORE_CORE_MSG_SERVICE_RESET, +} vservice_core_message_id_t; +typedef enum { + VSERVICE_CORE_NBIT_IN__COUNT +} vservice_core_nbit_in_t; + +typedef enum { + VSERVICE_CORE_NBIT_OUT__COUNT +} vservice_core_nbit_out_t; + +/* Notification mask macros */ +#endif /* ! __VSERVICES_CORE_PROTOCOL_H__ */ diff --git a/include/vservices/protocol/core/server.h b/include/vservices/protocol/core/server.h new file mode 100644 index 000000000000..959b8c3293bd --- /dev/null +++ b/include/vservices/protocol/core/server.h @@ -0,0 +1,171 @@ + +/* + * 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. + */ + +#if !defined(VSERVICES_SERVER_CORE) +#define VSERVICES_SERVER_CORE + +struct vs_service_device; +struct vs_server_core_state; + +struct vs_server_core { + + /* + * If set to false then the receive message handlers are run from + * workqueue context and are allowed to sleep. If set to true the + * message handlers are run from tasklet context and may not sleep. + */ + bool rx_atomic; + + /* + * If this is set to true along with rx_atomic, the driver is allowed + * to send messages from softirq contexts other than the receive + * message handlers, after calling vs_service_state_lock_bh. Otherwise, + * messages may only be sent from the receive message handlers, or + * from task context after calling vs_service_state_lock. This must + * not be set to true if rx_atomic is set to false. + */ + bool tx_atomic; + + /* + * These are the driver's recommended message quotas. They are used + * by the core service to select message quotas for services with no + * explicitly configured quotas. + */ + u32 in_quota_best; + u32 out_quota_best; + /** session setup **/ + struct vs_server_core_state *(*alloc) (struct vs_service_device * + service); + void (*release) (struct vs_server_core_state * _state); + + struct vs_service_driver *driver; + + /** Core service base interface **/ + void (*start) (struct vs_server_core_state * _state); + void (*reset) (struct vs_server_core_state * _state); + /** Send/receive state callbacks **/ + int (*tx_ready) (struct vs_server_core_state * _state); + + struct { + int (*state_change) (struct vs_server_core_state * _state, + vservice_core_statenum_t old, + vservice_core_statenum_t new); + + int (*req_connect) (struct vs_server_core_state * _state); + + int (*req_disconnect) (struct vs_server_core_state * _state); + + int (*msg_service_reset) (struct vs_server_core_state * _state, + uint32_t service_id); + + } core; +}; + +struct vs_server_core_state { + vservice_core_protocol_state_t state; + struct vs_service_device *service; + bool released; +}; + +/** Complete calls for server core functions **/ + + /** interface core **/ +/* command sync connect */ +extern int vs_server_core_core_send_ack_connect(struct vs_server_core_state + *_state, gfp_t flags); +extern int vs_server_core_core_send_nack_connect(struct vs_server_core_state + *_state, gfp_t flags); + /* command sync disconnect */ +extern int vs_server_core_core_send_ack_disconnect(struct vs_server_core_state + *_state, gfp_t flags); +extern int vs_server_core_core_send_nack_disconnect(struct vs_server_core_state + *_state, gfp_t flags); + /* message startup */ +extern int vs_server_core_core_send_startup(struct vs_server_core_state *_state, + uint32_t core_in_quota, + uint32_t core_out_quota, + gfp_t flags); + + /* message shutdown */ +extern int vs_server_core_core_send_shutdown(struct vs_server_core_state + *_state, gfp_t flags); + + /* message service_created */ +extern struct vs_mbuf *vs_server_core_core_alloc_service_created(struct + vs_server_core_state + *_state, + struct + vs_string + *service_name, + struct + vs_string + *protocol_name, + gfp_t flags); +extern int vs_server_core_core_free_service_created(struct vs_server_core_state + *_state, + struct vs_string + *service_name, + struct vs_string + *protocol_name, + struct vs_mbuf *_mbuf); +extern int vs_server_core_core_send_service_created(struct vs_server_core_state + *_state, + uint32_t service_id, + struct vs_string + service_name, + struct vs_string + protocol_name, + struct vs_mbuf *_mbuf); + + /* message service_removed */ +extern int vs_server_core_core_send_service_removed(struct vs_server_core_state + *_state, + uint32_t service_id, + gfp_t flags); + + /* message server_ready */ +extern int vs_server_core_core_send_server_ready(struct vs_server_core_state + *_state, uint32_t service_id, + uint32_t in_quota, + uint32_t out_quota, + uint32_t in_bit_offset, + uint32_t in_num_bits, + uint32_t out_bit_offset, + uint32_t out_num_bits, + gfp_t flags); + + /* message service_reset */ +extern int vs_server_core_core_send_service_reset(struct vs_server_core_state + *_state, uint32_t service_id, + gfp_t flags); + +/** Module registration **/ + +struct module; + +extern int __vservice_core_server_register(struct vs_server_core *server, + const char *name, + struct module *owner); + +static inline int vservice_core_server_register(struct vs_server_core *server, + const char *name) +{ +#ifdef MODULE + extern struct module __this_module; + struct module *this_module = &__this_module; +#else + struct module *this_module = NULL; +#endif + + return __vservice_core_server_register(server, name, this_module); +} + +extern int vservice_core_server_unregister(struct vs_server_core *server); +#endif /* ! VSERVICES_SERVER_CORE */ diff --git a/include/vservices/protocol/core/types.h b/include/vservices/protocol/core/types.h new file mode 100644 index 000000000000..2d6928dc0e06 --- /dev/null +++ b/include/vservices/protocol/core/types.h @@ -0,0 +1,87 @@ + +/* + * 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. + */ + +#if !defined(VSERVICES_CORE_TYPES_H) +#define VSERVICES_CORE_TYPES_H + +#define VSERVICE_CORE_SERVICE_NAME_SIZE (uint32_t)16 + +#define VSERVICE_CORE_PROTOCOL_NAME_SIZE (uint32_t)32 + +typedef enum { +/* state offline */ + VSERVICE_CORE_STATE_OFFLINE = 0, + VSERVICE_CORE_STATE_OFFLINE__CONNECT, + VSERVICE_CORE_STATE_OFFLINE__DISCONNECT, + +/* state disconnected */ + VSERVICE_CORE_STATE_DISCONNECTED, + VSERVICE_CORE_STATE_DISCONNECTED__CONNECT, + VSERVICE_CORE_STATE_DISCONNECTED__DISCONNECT, + +/* state connected */ + VSERVICE_CORE_STATE_CONNECTED, + VSERVICE_CORE_STATE_CONNECTED__CONNECT, + VSERVICE_CORE_STATE_CONNECTED__DISCONNECT, + + VSERVICE_CORE__RESET = VSERVICE_CORE_STATE_OFFLINE +} vservice_core_statenum_t; + +typedef struct { + vservice_core_statenum_t statenum; +} vservice_core_state_t; + +#define VSERVICE_CORE_RESET_STATE (vservice_core_state_t) { \ +.statenum = VSERVICE_CORE__RESET} + +#define VSERVICE_CORE_STATE_IS_OFFLINE(state) (\ +((state).statenum == VSERVICE_CORE_STATE_OFFLINE) || \ +((state).statenum == VSERVICE_CORE_STATE_OFFLINE__CONNECT) || \ +((state).statenum == VSERVICE_CORE_STATE_OFFLINE__DISCONNECT)) + +#define VSERVICE_CORE_STATE_IS_DISCONNECTED(state) (\ +((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED) || \ +((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED__CONNECT) || \ +((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED__DISCONNECT)) + +#define VSERVICE_CORE_STATE_IS_CONNECTED(state) (\ +((state).statenum == VSERVICE_CORE_STATE_CONNECTED) || \ +((state).statenum == VSERVICE_CORE_STATE_CONNECTED__CONNECT) || \ +((state).statenum == VSERVICE_CORE_STATE_CONNECTED__DISCONNECT)) + +#define VSERVICE_CORE_STATE_VALID(state) ( \ +VSERVICE_CORE_STATE_IS_OFFLINE(state) ? true : \ +VSERVICE_CORE_STATE_IS_DISCONNECTED(state) ? true : \ +VSERVICE_CORE_STATE_IS_CONNECTED(state) ? true : \ +false) + +static inline const char *vservice_core_get_state_string(vservice_core_state_t + state) +{ + static const char *names[] = + { "offline", "offline__connect", "offline__disconnect", + "disconnected", "disconnected__connect", + "disconnected__disconnect", + "connected", "connected__connect", "connected__disconnect" + }; + if (!VSERVICE_CORE_STATE_VALID(state)) { + return "INVALID"; + } + return names[state.statenum]; +} + +typedef struct { + + vservice_core_state_t core; +} vservice_core_protocol_state_t; + +#define VSERVICE_CORE_PROTOCOL_RESET_STATE (vservice_core_protocol_state_t) {\ +.core = VSERVICE_CORE_RESET_STATE } +#endif /* ! VSERVICES_CORE_TYPES_H */ diff --git a/include/vservices/service.h b/include/vservices/service.h new file mode 100644 index 000000000000..af232b63947a --- /dev/null +++ b/include/vservices/service.h @@ -0,0 +1,674 @@ +/* + * include/vservices/service.h + * + * 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. + * + * This file defines the driver and device types for vServices client and + * server drivers. These are generally defined by generated protocol-layer + * code. However, they can also be defined directly by applications that + * don't require protocol generation. + */ + +#ifndef _VSERVICE_SERVICE_H_ +#define _VSERVICE_SERVICE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 38) +#include +#else +#include +#endif + +#include +#include +#include + +struct vs_mbuf; + +/** + * struct vs_service_driver - Virtual service driver structure + * @protocol: Protocol name for this driver + * @is_server: True if this is a server driver, false if it is a client driver + * @rx_atomic: If set to false then the receive message handlers are run from + * workqueue context and are allowed to sleep. If set to true + * the message handlers are run from tasklet context and may not + * sleep. For this purpose, tx_ready is considered a receive + * message handler. + * @tx_atomic: If this is set to true along with rx_atomic, the driver is + * allowed to send messages from softirq contexts other than the receive + * message handlers, after calling vs_service_state_lock_bh. Otherwise, + * messages may only be sent from the receive message handlers, or from + * task context after calling vs_service_state_lock. + * @probe: Probe function for this service + * @remove: Remove function for this service + * --- Callbacks --- + * @receive: Message handler function for this service + * @notify: Incoming notification handler function for this service + * @start: Callback which is run when this service is started + * @reset: Callback which is run when this service is reset + * @tx_ready: Callback which is run when the service has dropped below its + * send quota + * --- Resource requirements (valid for server only) --- + * @in_quota_min: minimum number of input messages for protocol functionality + * @in_quota_best: suggested number of input messages + * @out_quota_min: minimum number of output messages for protocol functionality + * @out_quota_best: suggested number of output messages + * @in_notify_count: number of input notification bits used + * @out_notify_count: number of output notification bits used + * --- Internal --- + * @driver: Linux device model driver structure + * + * The callback functions for a virtual service driver are all called from + * the virtual service device's work queue. + */ +struct vs_service_driver { + const char *protocol; + bool is_server; + bool rx_atomic, tx_atomic; + + int (*probe)(struct vs_service_device *service); + int (*remove)(struct vs_service_device *service); + + int (*receive)(struct vs_service_device *service, + struct vs_mbuf *mbuf); + void (*notify)(struct vs_service_device *service, u32 flags); + + void (*start)(struct vs_service_device *service); + void (*reset)(struct vs_service_device *service); + + int (*tx_ready)(struct vs_service_device *service); + + unsigned in_quota_min; + unsigned in_quota_best; + unsigned out_quota_min; + unsigned out_quota_best; + unsigned in_notify_count; + unsigned out_notify_count; + + struct device_driver driver; +}; + +#define to_vs_service_driver(d) \ + container_of(d, struct vs_service_driver, driver) + +/* The vServices server/client bus types */ +extern struct bus_type vs_client_bus_type; +extern struct bus_type vs_server_bus_type; + +/** + * struct vs_service_stats - Virtual service statistics + * @over_quota_time: Internal counter for tracking over quota time. + * @sent_mbufs: Total number of message buffers sent. + * @sent_bytes: Total bytes sent. + * @send_failures: Total number of send failures. + * @recv_mbufs: Total number of message buffers received. + * @recv_bytes: Total number of bytes recevied. + * @recv_failures: Total number of receive failures. + * @nr_over_quota: Number of times an mbuf allocation has failed because the + * service is over quota. + * @nr_tx_ready: Number of times the service has run its tx_ready handler + * @over_quota_time_total: The total amount of time in milli-seconds that the + * service has spent over quota. Measured as the time + * between exceeding quota in mbuf allocation and + * running the tx_ready handler. + * @over_quota_time_avg: The average amount of time in milli-seconds that the + * service is spending in the over quota state. + */ +struct vs_service_stats { + unsigned long over_quota_time; + + atomic_t sent_mbufs; + atomic_t sent_bytes; + atomic_t send_failures; + atomic_t recv_mbufs; + atomic_t recv_bytes; + atomic_t recv_failures; + atomic_t nr_over_quota; + atomic_t nr_tx_ready; + atomic_t over_quota_time_total; + atomic_t over_quota_time_avg; +}; + +/** + * struct vs_service_device - Virtual service device + * @id: Unique ID (to the session) for this service + * @name: Service name + * @sysfs_name: The sysfs name for the service + * @protocol: Service protocol name + * @is_server: True if this device is server, false if it is a client + * @owner: service responsible for managing this service. This must be + * on the same session, and is NULL iff this is the core service. + * It must not be a service whose driver has tx_atomic set. + * @lock_subclass: the number of generations of owners between this service + * and the core service; 0 for the core service, 1 for anything directly + * created by it, and so on. This is only used for verifying lock + * ordering (when lockdep is enabled), hence the name. + * @ready_lock: mutex protecting readiness, disable_count and driver_probed. + * This depends on the state_mutex of the service's owner, if any. Acquire + * it using mutex_lock_nested(ready_lock, lock_subclass). + * @readiness: Service's readiness state, owned by session layer. + * @disable_count: Number of times the service has been disabled without + * a matching enable. + * @driver_probed: True if a driver has been probed (and not removed) + * @work_queue: Work queue for this service's task-context work. + * @rx_tasklet: Tasklet for handling incoming messages. This is only used + * if the service driver has rx_atomic set to true. Otherwise + * incoming messages are handled on the workqueue by rx_work. + * @rx_work: Work structure for handling incoming messages. This is only + * used if the service driver has rx_atomic set to false. + * @rx_lock: Spinlock which protects access to rx_queue and tx_ready + * @rx_queue: Queue of incoming messages + * @tx_ready: Flag indicating that a tx_ready event is pending + * @tx_batching: Flag indicating that outgoing messages are being batched + * @state_spinlock: spinlock used to protect the service state if the + * service driver has tx_atomic (and rx_atomic) set to true. This + * depends on the service's ready_lock. Acquire it only by + * calling vs_service_state_lock_bh(). + * @state_mutex: mutex used to protect the service state if the service + * driver has tx_atomic set to false. This depends on the service's + * ready_lock, and if rx_atomic is true, the rx_tasklet must be + * disabled while it is held. Acquire it only by calling + * vs_service_state_lock(). + * @state_spinlock_used: Flag to check if the state spinlock has been acquired. + * @state_mutex_used: Flag to check if the state mutex has been acquired. + * @reset_work: Work to reset the service after a driver fails + * @pending_reset: Set if reset_work has been queued and not completed. + * @ready_work: Work to make service ready after a throttling delay + * @cooloff_work: Work for cooling off reset throttling after the reset + * throttling limit was hit + * @cleanup_work: Work for cleaning up and freeing the service structure + * @last_reset: Time in jiffies at which this service last reset + * @last_reset_request: Time in jiffies the last reset request for this + * service occurred at + * @last_ready: Time in jiffies at which this service last became ready + * @reset_delay: Time in jiffies that the next throttled reset will be + * delayed for. A value of zero means that reset throttling is not in + * effect. + * @is_over_quota: Internal flag for whether the service is over quota. This + * flag is only used for stats accounting. + * @quota_wq: waitqueue that is woken whenever the available send quota + * increases. + * @notify_send_bits: The number of bits allocated for outgoing notifications. + * @notify_send_offset: The first bit allocated for outgoing notifications. + * @notify_recv_bits: The number of bits allocated for incoming notifications. + * @notify_recv_offset: The first bit allocated for incoming notifications. + * @send_quota: The maximum number of outgoing messages. + * @recv_quota: The maximum number of incoming messages. + * @in_quota_set: For servers, the number of client->server messages + * requested during system configuration (sysfs or environment). + * @out_quota_set: For servers, the number of server->client messages + * requested during system configuration (sysfs or environment). + * @dev: Linux device model device structure + * @stats: Service statistics + */ +struct vs_service_device { + vs_service_id_t id; + char *name; + char *sysfs_name; + char *protocol; + bool is_server; + + struct vs_service_device *owner; + unsigned lock_subclass; + + struct mutex ready_lock; + unsigned readiness; + int disable_count; + bool driver_probed; + + struct workqueue_struct *work_queue; + + struct tasklet_struct rx_tasklet; + struct work_struct rx_work; + + spinlock_t rx_lock; + struct list_head rx_queue; + bool tx_ready, tx_batching; + + spinlock_t state_spinlock; + struct mutex state_mutex; + + struct work_struct reset_work; + bool pending_reset; + struct delayed_work ready_work; + struct delayed_work cooloff_work; + struct work_struct cleanup_work; + + unsigned long last_reset; + unsigned long last_reset_request; + unsigned long last_ready; + unsigned long reset_delay; + + atomic_t is_over_quota; + wait_queue_head_t quota_wq; + + unsigned notify_send_bits; + unsigned notify_send_offset; + unsigned notify_recv_bits; + unsigned notify_recv_offset; + unsigned send_quota; + unsigned recv_quota; + + unsigned in_quota_set; + unsigned out_quota_set; + + void *transport_priv; + + struct device dev; + struct vs_service_stats stats; + +#ifdef CONFIG_VSERVICES_LOCK_DEBUG + bool state_spinlock_used; + bool state_mutex_used; +#endif +}; + +#define to_vs_service_device(d) container_of(d, struct vs_service_device, dev) + +/** + * vs_service_get_session - Return the session for a service + * @service: Service to get the session for + */ +static inline struct vs_session_device * +vs_service_get_session(struct vs_service_device *service) +{ + return to_vs_session_device(service->dev.parent); +} + +/** + * vs_service_send - Send a message from a service + * @service: Service to send the message from + * @mbuf: Message buffer to send + */ +static inline int +vs_service_send(struct vs_service_device *service, struct vs_mbuf *mbuf) +{ + struct vs_session_device *session = vs_service_get_session(service); + const struct vs_transport_vtable *vt = session->transport->vt; + const unsigned long flags = + service->tx_batching ? VS_TRANSPORT_SEND_FLAGS_MORE : 0; + size_t msg_size = vt->mbuf_size(mbuf); + int err; + + err = vt->send(session->transport, service, mbuf, flags); + if (!err) { + atomic_inc(&service->stats.sent_mbufs); + atomic_add(msg_size, &service->stats.sent_bytes); + } else { + atomic_inc(&service->stats.send_failures); + } + + return err; +} + +/** + * vs_service_alloc_mbuf - Allocate a message buffer for a service + * @service: Service to allocate the buffer for + * @size: Size of the data buffer to allocate + * @flags: Flags to pass to the buffer allocation + */ +static inline struct vs_mbuf * +vs_service_alloc_mbuf(struct vs_service_device *service, size_t size, + gfp_t flags) +{ + struct vs_session_device *session = vs_service_get_session(service); + struct vs_mbuf *mbuf; + + mbuf = session->transport->vt->alloc_mbuf(session->transport, + service, size, flags); + if (IS_ERR(mbuf) && PTR_ERR(mbuf) == -ENOBUFS) { + /* Over quota accounting */ + if (atomic_cmpxchg(&service->is_over_quota, 0, 1) == 0) { + service->stats.over_quota_time = jiffies; + atomic_inc(&service->stats.nr_over_quota); + } + } + + /* + * The transport drivers should return either a valid message buffer + * pointer or an ERR_PTR value. Warn here if a transport driver is + * returning NULL on message buffer allocation failure. + */ + if (WARN_ON_ONCE(!mbuf)) + return ERR_PTR(-ENOMEM); + + return mbuf; +} + +/** + * vs_service_free_mbuf - Deallocate a message buffer for a service + * @service: Service the message buffer was allocated for + * @mbuf: Message buffer to deallocate + */ +static inline void +vs_service_free_mbuf(struct vs_service_device *service, struct vs_mbuf *mbuf) +{ + struct vs_session_device *session = vs_service_get_session(service); + + session->transport->vt->free_mbuf(session->transport, service, mbuf); +} + +/** + * vs_service_notify - Send a notification from a service + * @service: Service to send the notification from + * @flags: Notification bits to send + */ +static inline int +vs_service_notify(struct vs_service_device *service, u32 flags) +{ + struct vs_session_device *session = vs_service_get_session(service); + + return session->transport->vt->notify(session->transport, + service, flags); +} + +/** + * vs_service_has_atomic_rx - Return whether or not a service's receive + * message handler runs in atomic context. This function should only be + * called for services which are bound to a driver. + * + * @service: Service to check + */ +static inline bool +vs_service_has_atomic_rx(struct vs_service_device *service) +{ + if (WARN_ON(!service->dev.driver)) + return false; + + return to_vs_service_driver(service->dev.driver)->rx_atomic; +} + +/** + * vs_session_max_mbuf_size - Return the maximum allocation size of a message + * buffer. + * @service: The service to check + */ +static inline size_t +vs_service_max_mbuf_size(struct vs_service_device *service) +{ + struct vs_session_device *session = vs_service_get_session(service); + + return session->transport->vt->max_mbuf_size(session->transport); +} + +/** + * vs_service_send_mbufs_available - Return the number of mbufs which can be + * allocated for sending before going over quota. + * @service: The service to check + */ +static inline ssize_t +vs_service_send_mbufs_available(struct vs_service_device *service) +{ + struct vs_session_device *session = vs_service_get_session(service); + + return session->transport->vt->service_send_avail(session->transport, + service); +} + +/** + * vs_service_has_atomic_tx - Return whether or not a service is allowed to + * transmit from atomic context (other than its receive message handler). + * This function should only be called for services which are bound to a + * driver. + * + * @service: Service to check + */ +static inline bool +vs_service_has_atomic_tx(struct vs_service_device *service) +{ + if (WARN_ON(!service->dev.driver)) + return false; + + return to_vs_service_driver(service->dev.driver)->tx_atomic; +} + +/** + * vs_service_state_lock - Acquire a lock allowing service state operations + * from external task contexts. + * + * @service: Service to lock. + * + * This must be used to protect any service state accesses that occur in task + * contexts outside of a callback from the vservices protocol layer. It must + * not be called from a protocol layer callback, nor from atomic context. + * + * If this service's state is also accessed from softirq contexts other than + * vservices protocol layer callbacks, use vs_service_state_lock_bh instead, + * and set the driver's tx_atomic flag. + * + * If this is called from outside the service's workqueue, the calling driver + * must provide its own guarantee that it has not been detached from the + * service. If that is not possible, use vs_state_lock_safe(). + */ +static inline void +vs_service_state_lock(struct vs_service_device *service) +__acquires(service) +{ +#ifdef CONFIG_VSERVICES_LOCK_DEBUG + WARN_ON_ONCE(vs_service_has_atomic_tx(service)); +#endif + + mutex_lock_nested(&service->state_mutex, service->lock_subclass); + +#ifdef CONFIG_VSERVICES_LOCK_DEBUG + if (WARN_ON_ONCE(service->state_spinlock_used)) + dev_err(&service->dev, "Service is using both the state spinlock and mutex - Fix your driver\n"); + service->state_mutex_used = true; +#endif + + if (vs_service_has_atomic_rx(service)) + tasklet_disable(&service->rx_tasklet); + + __acquire(service); +} + +/** + * vs_service_state_unlock - Release the lock acquired by vs_service_state_lock. + * + * @service: Service to unlock. + */ +static inline void +vs_service_state_unlock(struct vs_service_device *service) +__releases(service) +{ + __release(service); + + mutex_unlock(&service->state_mutex); + + if (vs_service_has_atomic_rx(service)) { + tasklet_enable(&service->rx_tasklet); + + /* Kick the tasklet if there is RX work to do */ + if (!list_empty(&service->rx_queue)) + tasklet_schedule(&service->rx_tasklet); + } +} + +/** + * vs_service_state_lock_bh - Acquire a lock allowing service state operations + * from external task or softirq contexts. + * + * @service: Service to lock. + * + * This is an alternative to vs_service_state_lock for drivers that receive + * messages in atomic context (i.e. have their rx_atomic flag set), *and* must + * transmit messages from softirq contexts other than their own message + * receive and tx_ready callbacks. Such drivers must set their tx_atomic + * flag, so generated protocol drivers perform correct locking. + * + * This should replace all calls to vs_service_state_lock for services that + * need it. Do not use both locking functions in one service driver. + * + * The calling driver must provide its own guarantee that it has not been + * detached from the service. If that is not possible, use + * vs_state_lock_safe_bh(). + */ +static inline void +vs_service_state_lock_bh(struct vs_service_device *service) +__acquires(service) +__acquires(&service->state_spinlock) +{ +#ifdef CONFIG_VSERVICES_LOCK_DEBUG + WARN_ON_ONCE(!vs_service_has_atomic_rx(service)); + WARN_ON_ONCE(!vs_service_has_atomic_tx(service)); +#endif + +#ifdef CONFIG_SMP + /* Not necessary on UP because it's implied by spin_lock_bh(). */ + tasklet_disable(&service->rx_tasklet); +#endif + + spin_lock_bh(&service->state_spinlock); + +#ifdef CONFIG_VSERVICES_LOCK_DEBUG + if (WARN_ON_ONCE(service->state_mutex_used)) + dev_err(&service->dev, "Service is using both the state spinlock and mutex - Fix your driver\n"); + service->state_spinlock_used = true; +#endif + + __acquire(service); +} + +/** + * vs_service_state_unlock_bh - Release the lock acquired by + * vs_service_state_lock_bh. + * + * @service: Service to unlock. + */ +static inline void +vs_service_state_unlock_bh(struct vs_service_device *service) +__releases(service) +__releases(&service->state_spinlock) +{ + __release(service); + + spin_unlock_bh(&service->state_spinlock); + +#ifdef CONFIG_SMP + tasklet_enable(&service->rx_tasklet); +#endif +} + +/* Convenience macros for locking a state structure rather than a service. */ +#define vs_state_lock(state) vs_service_state_lock((state)->service) +#define vs_state_unlock(state) vs_service_state_unlock((state)->service) +#define vs_state_lock_bh(state) vs_service_state_lock_bh((state)->service) +#define vs_state_unlock_bh(state) vs_service_state_unlock_bh((state)->service) + +/** + * vs_state_lock_safe[_bh] - Aqcuire a lock for a state structure's service, + * when the service may have been detached from the state. + * + * This is useful for blocking operations that can't easily be terminated + * before returning from the service reset handler, such as file I/O. To use + * this, the state structure should be reference-counted rather than freed in + * the release callback, and the driver should retain its own reference to the + * service until the state structure is freed. + * + * This macro acquires the lock and returns true if the state has not been + * detached from the service. Otherwise, it returns false. + * + * Note that the _bh variant cannot be used from atomic context, because it + * acquires a mutex. + */ +#define __vs_state_lock_safe(_state, _lock, _unlock) ({ \ + bool __ok = true; \ + typeof(_state) __state = (_state); \ + struct vs_service_device *__service = __state->service; \ + mutex_lock_nested(&__service->ready_lock, \ + __service->lock_subclass); \ + __ok = !ACCESS_ONCE(__state->released); \ + if (__ok) { \ + _lock(__state); \ + __ok = !ACCESS_ONCE(__state->released); \ + if (!__ok) \ + _unlock(__state); \ + } \ + mutex_unlock(&__service->ready_lock); \ + __ok; \ +}) +#define vs_state_lock_safe(_state) \ + __vs_state_lock_safe((_state), vs_state_lock, vs_state_unlock) +#define vs_state_lock_safe_bh(_state) \ + __vs_state_lock_safe((_state), vs_state_lock_bh, vs_state_unlock_bh) + +/** + * vs_get_service - Get a reference to a service. + * @service: Service to get a reference to. + */ +static inline struct vs_service_device * +vs_get_service(struct vs_service_device *service) +{ + if (service) + get_device(&service->dev); + return service; +} + +/** + * vs_put_service - Put a reference to a service. + * @service: The service to put the reference to. + */ +static inline void +vs_put_service(struct vs_service_device *service) +{ + put_device(&service->dev); +} + +extern int vs_service_reset(struct vs_service_device *service, + struct vs_service_device *caller); +extern void vs_service_reset_nosync(struct vs_service_device *service); + +/** + * vs_service_send_batch_start - Start a batch of outgoing messages + * @service: The service that is starting a batch + * @flush: Finish any previously started batch (if false, then duplicate + * calls to this function have no effect) + */ +static inline void +vs_service_send_batch_start(struct vs_service_device *service, bool flush) +{ + if (flush && service->tx_batching) { + struct vs_session_device *session = + vs_service_get_session(service); + const struct vs_transport_vtable *vt = session->transport->vt; + if (vt->flush) + vt->flush(session->transport, service); + } else { + service->tx_batching = true; + } +} + +/** + * vs_service_send_batch_end - End a batch of outgoing messages + * @service: The service that is ending a batch + * @flush: Start sending the batch immediately (if false, the batch will + * be flushed when the next message is sent) + */ +static inline void +vs_service_send_batch_end(struct vs_service_device *service, bool flush) +{ + service->tx_batching = false; + if (flush) { + struct vs_session_device *session = + vs_service_get_session(service); + const struct vs_transport_vtable *vt = session->transport->vt; + if (vt->flush) + vt->flush(session->transport, service); + } +} + + +#endif /* _VSERVICE_SERVICE_H_ */ diff --git a/include/vservices/session.h b/include/vservices/session.h new file mode 100644 index 000000000000..b9dc775dc9f4 --- /dev/null +++ b/include/vservices/session.h @@ -0,0 +1,161 @@ +/* + * include/vservices/session.h + * + * 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. + * + * This file defines the device type for a vServices session attached to a + * transport. This should only be used by transport drivers, the vServices + * session code, and the inline transport-access functions defined in + * vservices/service.h. + * + * Drivers for these devices are defined internally by the vServices + * framework. Other drivers should not attach to these devices. + */ + +#ifndef _VSERVICES_SESSION_H_ +#define _VSERVICES_SESSION_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include + +struct vs_service_device; +struct vs_mbuf; + +struct notifier_block; + +/** + * enum vs_notify_event_t - vService notifier events + * + * @VS_SESSION_NOTIFY_ADD: vService session added. Argument is a pointer to + * the vs_session_device. This notification is sent after the session has been + * added. + * + * @VS_SESSION_NOTIFY_REMOVE: vService session about to be removed. Argument is + * a pointer to the vs_session_device. This notification is sent before the + * session is removed. + */ +enum vs_notify_event_t { + VS_SESSION_NOTIFY_ADD, + VS_SESSION_NOTIFY_REMOVE, +}; + +/** + * struct vs_session_device - Session device + * @name: The unique human-readable name of this session. + * @is_server: True if this session is a server, false if client + * @transport: The transport device for this session + * @session_num: Unique ID for this session. Used for sysfs + * @session_lock: Mutex which protects any change to service presence or + * readiness + * @core_service: The core service, if one has ever been registered. Once set, + * this must remain valid and unchanged until the session driver is + * removed. Writes are protected by the service_ids_lock. + * @services: Dynamic array of the services on this session. Protected by + * service_ids_lock. + * @alloc_service_ids: Size of the session services array + * @service_ids_lock: Mutex protecting service array updates + * @activation_work: work structure for handling session activation & reset + * @activation_state: true if transport is currently active + * @fatal_error_work: work structure for handling fatal session failures + * @debug_mask: Debug level mask + * @list: Entry in the global session list + * @sysfs_entry: Kobject pointer pointing to session device in sysfs under + * sys/vservices + * @dev: Device structure for the Linux device model + */ +struct vs_session_device { + char *name; + bool is_server; + struct vs_transport *transport; + int session_num; + + struct mutex session_lock; + + /* + * The service_idr maintains the list of currently allocated services + * on a session, and allows for recycling of service ids. The lock also + * protects core_service. + */ + struct idr service_idr; + struct mutex service_idr_lock; + struct vs_service_device *core_service; + + struct work_struct activation_work; + atomic_t activation_state; + + struct work_struct fatal_error_work; + + unsigned long debug_mask; + + struct list_head list; + struct kobject *sysfs_entry; + + struct device dev; +}; + +#define to_vs_session_device(d) \ + container_of(d, struct vs_session_device, dev) + +extern struct vs_session_device * +vs_session_register(struct vs_transport *transport, struct device *parent, + bool server, const char *transport_name); +extern void vs_session_start(struct vs_session_device *session); +extern void vs_session_unregister(struct vs_session_device *session); + +extern int vs_session_handle_message(struct vs_session_device *session, + struct vs_mbuf *mbuf, vs_service_id_t service_id); + +extern void vs_session_quota_available(struct vs_session_device *session, + vs_service_id_t service_id, unsigned count, + bool send_tx_ready); + +extern void vs_session_handle_notify(struct vs_session_device *session, + unsigned long flags, vs_service_id_t service_id); + +extern void vs_session_handle_reset(struct vs_session_device *session); +extern void vs_session_handle_activate(struct vs_session_device *session); + +extern struct vs_service_device * +vs_server_create_service(struct vs_session_device *session, + struct vs_service_device *parent, const char *name, + const char *protocol, const void *plat_data); +extern int vs_server_destroy_service(struct vs_service_device *service, + struct vs_service_device *parent); + +extern void vs_session_register_notify(struct notifier_block *nb); +extern void vs_session_unregister_notify(struct notifier_block *nb); + +extern int vs_session_unbind_driver(struct vs_service_device *service); + +extern void vs_session_for_each_service(struct vs_session_device *session, + void (*func)(struct vs_service_device *, void *), void *data); + +extern struct mutex vs_session_lock; +extern int vs_session_for_each_locked( + int (*fn)(struct vs_session_device *session, void *data), + void *data); + +static inline int vs_session_for_each( + int (*fn)(struct vs_session_device *session, void *data), + void *data) +{ + int r; + mutex_lock(&vs_session_lock); + r = vs_session_for_each_locked(fn, data); + mutex_unlock(&vs_session_lock); + return r; +} + +#endif /* _VSERVICES_SESSION_H_ */ diff --git a/include/vservices/transport.h b/include/vservices/transport.h new file mode 100644 index 000000000000..6251ce15684a --- /dev/null +++ b/include/vservices/transport.h @@ -0,0 +1,150 @@ +/* + * include/vservices/transport.h + * + * 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. + * + * This file contains the transport vtable structure. This is made public so + * that the application drivers can call the vtable functions directly (via + * the inlined wrappers in service.h) rather than indirectly via a function + * call. + * + */ + +#ifndef _VSERVICES_TRANSPORT_H_ +#define _VSERVICES_TRANSPORT_H_ + +#include + +#include + +struct vs_transport; +struct vs_mbuf; +struct vs_service_device; + +/** + * struct vs_transport_vtable - Transport driver operations. Transport drivers + * must provide implementations for all operations in this table. + * --- Message buffer allocation --- + * @alloc_mbuf: Allocate an mbuf of the given size for the given service + * @free_mbuf: Deallocate an mbuf + * @mbuf_size: Return the size in bytes of a message buffer. The size returned + * should be the total number of bytes including any headers. + * @max_mbuf_size: Return the maximum allowable message buffer allocation size. + * --- Message sending --- + * @send: Queue an mbuf for sending + * @flush: Start the transfer for the current message batch, if any + * @notify: Send a notification + * --- Transport-level reset handling --- + * @reset: Reset the transport layer + * @ready: Ready the transport layer + * --- Service management --- + * @service_add: A new service has been added to this transport's session + * @service_remove: A service has been removed from this transport's session + * @service_start: A service on this transport's session has had its resource + * allocations set and is about to start. This is always interleaved with + * service_reset, with one specific exception: the core service client, + * which has its quotas initially hard-coded to 0 send / 1 recv and + * adjusted when the initial startup message arrives. + * @service_reset: A service on this transport's session has just been reset, + * and any resources allocated to it should be cleaned up to prepare + * for later reallocation. + * @service_send_avail: The number of message buffers that this service is + * able to send before going over quota. + * --- Query transport capabilities --- + * @get_notify_bits: Fetch the number of sent and received notification bits + * supported by this transport. Note that this can be any positive value + * up to UINT_MAX. + * @get_quota_limits: Fetch the total send and receive message buffer quotas + * supported by this transport. Note that this can be any positive value + * up to UINT_MAX. + */ +struct vs_transport_vtable { + /* Message buffer allocation */ + struct vs_mbuf *(*alloc_mbuf)(struct vs_transport *transport, + struct vs_service_device *service, size_t size, + gfp_t gfp_flags); + void (*free_mbuf)(struct vs_transport *transport, + struct vs_service_device *service, + struct vs_mbuf *mbuf); + size_t (*mbuf_size)(struct vs_mbuf *mbuf); + size_t (*max_mbuf_size)(struct vs_transport *transport); + + /* Sending messages */ + int (*send)(struct vs_transport *transport, + struct vs_service_device *service, + struct vs_mbuf *mbuf, unsigned long flags); + int (*flush)(struct vs_transport *transport, + struct vs_service_device *service); + int (*notify)(struct vs_transport *transport, + struct vs_service_device *service, + unsigned long bits); + + /* Raising and clearing transport-level reset */ + void (*reset)(struct vs_transport *transport); + void (*ready)(struct vs_transport *transport); + + /* Service management */ + int (*service_add)(struct vs_transport *transport, + struct vs_service_device *service); + void (*service_remove)(struct vs_transport *transport, + struct vs_service_device *service); + + int (*service_start)(struct vs_transport *transport, + struct vs_service_device *service); + int (*service_reset)(struct vs_transport *transport, + struct vs_service_device *service); + + ssize_t (*service_send_avail)(struct vs_transport *transport, + struct vs_service_device *service); + + /* Query transport capabilities */ + void (*get_notify_bits)(struct vs_transport *transport, + unsigned *send_notify_bits, unsigned *recv_notify_bits); + void (*get_quota_limits)(struct vs_transport *transport, + unsigned *send_quota, unsigned *recv_quota); +}; + +/* Flags for .send */ +#define VS_TRANSPORT_SEND_FLAGS_MORE 0x1 + +/** + * struct vs_transport - A structure representing a transport + * @type: type of transport i.e. microvisror/loopback etc + * @vt: Transport operations table + * @notify_info: Array of incoming notification settings + * @notify_info_size: Size of the incoming notification array + */ +struct vs_transport { + const char *type; + const struct vs_transport_vtable *vt; + struct vs_notify_info *notify_info; + int notify_info_size; +}; + +/** + * struct vs_mbuf - Message buffer. This is always allocated and released by the + * transport callbacks defined above, so it may be embedded in a + * transport-specific structure containing additional state. + * @data: Message data buffer + * @size: Size of the data buffer in bytes + * @is_recv: True if this mbuf was received from the other end of the + * transport. False if it was allocated by this end for sending. + * @priv: Private value that will not be touched by the framework + * @queue: list_head for entry in lists. The session layer uses this queue + * for receiving messages. The transport driver may use this queue for its + * own purposes when sending messages. + */ +struct vs_mbuf { + void *data; + size_t size; + bool is_recv; + void *priv; + struct list_head queue; +}; + +#endif /* _VSERVICES_TRANSPORT_H_ */ diff --git a/include/vservices/types.h b/include/vservices/types.h new file mode 100644 index 000000000000..306156eab1ba --- /dev/null +++ b/include/vservices/types.h @@ -0,0 +1,41 @@ +/* + * include/vservices/types.h + * + * 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. + */ + +#ifndef _VSERVICE_TYPES_H +#define _VSERVICE_TYPES_H + +#include + +typedef u16 vs_service_id_t; +typedef u16 vs_message_id_t; + +/* + * An opaque handle to a queued asynchronous command. This is used internally + * by the generated interface code, to identify which of the pending commands + * is being replied to. It is provided as a parameter to non-blocking handler + * callbacks for queued asynchronous requests, and must be stored by the server + * and passed to the corresponding reply call. + */ +typedef struct vservice_queued_request vservice_queued_request_t; + +/* + * Following enum is to be used by server for informing about successful or + * unsuccessful open callback by using VS_SERVER_RESP_SUCCESS or + * VS_SERVER_RESP_FAILURE resepectively. Server can choose to complete request + * explicitely in this case it should return VS_SERVER_RESP_EXPLICIT_COMPLETE. + */ +typedef enum vs_server_response_type { + VS_SERVER_RESP_SUCCESS, + VS_SERVER_RESP_FAILURE, + VS_SERVER_RESP_EXPLICIT_COMPLETE +} vs_server_response_type_t; + +#endif /*_VSERVICE_TYPES_H */ diff --git a/include/vservices/wait.h b/include/vservices/wait.h new file mode 100644 index 000000000000..544937de2058 --- /dev/null +++ b/include/vservices/wait.h @@ -0,0 +1,455 @@ +/* + * include/vservices/wait.h + * + * 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. + * + * Generic wait event helpers for Virtual Service drivers. + */ + +#ifndef _VSERVICE_SERVICE_WAIT_H +#define _VSERVICE_SERVICE_WAIT_H + +#include +#include + +#include + +/* Older kernels don't have lockdep_assert_held_once(). */ +#ifndef lockdep_assert_held_once +#ifdef CONFIG_LOCKDEP +#define lockdep_assert_held_once(l) do { \ + WARN_ON_ONCE(debug_locks && !lockdep_is_held(l)); \ + } while (0) +#else +#define lockdep_assert_held_once(l) do { } while (0) +#endif +#endif + +/* Legacy wait macro; needs rewriting to use vs_state_lock_safe(). */ +/* FIXME: Redmine ticket #229 - philip. */ +/** + * __vs_service_wait_event - Wait for a condition to become true for a + * Virtual Service. + * + * @_service: The service to wait for the condition to be true for. + * @_wq: Waitqueue to wait on. + * @_condition: Condition to wait for. + * + * Returns: This function returns 0 if the condition is true, or a -ERESTARTSYS + * if the wait loop wait interrupted. If _state is TASK_UNINTERRUPTIBLE + * then this function will always return 0. + * + * This function must be called with the service's state lock held. The wait + * is performed without the state lock held, but the condition is re-checked + * after reacquiring the state lock. This property allows this function to + * check the state of the service's protocol in a thread safe manner. + * + * The caller is responsible for ensuring that it has not been detached from + * the given service. + * + * It is nearly always wrong to call this on the service workqueue, since + * the workqueue is single-threaded and the state can only change when a + * handler function is called on it. + */ +#define __vs_service_wait_event(_service, _wq, _cond, _state) \ + ({ \ + DEFINE_WAIT(__wait); \ + int __ret = 0; \ + \ + lockdep_assert_held_once(&(_service)->state_mutex); \ + do { \ + prepare_to_wait(&(_wq), &__wait, (_state)); \ + \ + if (_cond) \ + break; \ + \ + if ((_state) == TASK_INTERRUPTIBLE && \ + signal_pending(current)) { \ + __ret = -ERESTARTSYS; \ + break; \ + } \ + \ + vs_service_state_unlock(_service); \ + schedule(); \ + vs_service_state_lock(_service); \ + } while (!(_cond)); \ + \ + finish_wait(&(_wq), &__wait); \ + __ret; \ + }) + +/* Legacy wait macros; need rewriting to use __vs_wait_state(). */ +/* FIXME: Redmine ticket #229 - philip. */ +#define vs_service_wait_event(_service, _wq, _cond) \ + __vs_service_wait_event(_service, _wq, _cond, TASK_INTERRUPTIBLE) +#define vs_service_wait_event_nointr(_service, _wq, _cond) \ + __vs_service_wait_event(_service, _wq, _cond, TASK_UNINTERRUPTIBLE) + +/** + * __vs_wait_state - block until a condition becomes true on a service state. + * + * @_state: The protocol state to wait on. + * @_cond: Condition to wait for. + * @_intr: If true, perform an interruptible wait; the wait may then fail + * with -ERESTARTSYS. + * @_timeout: A timeout in jiffies, or negative for no timeout. If the + * timeout expires, the wait will fail with -ETIMEDOUT. + * @_bh: The token _bh if this service uses tx_atomic (sends from a + * non-framework tasklet); otherwise nothing. + * + * Return: Return a pointer to a message buffer on successful allocation, + * or an error code in ERR_PTR form. + * + * This macro blocks waiting until a particular condition becomes true on a + * service state. The service must be running; if not, or if it ceases to be + * running during the wait, -ECANCELED will be returned. + * + * This is not an exclusive wait. If an exclusive wait is desired it is + * usually better to use the waiting alloc or send functions. + * + * This macro must be called with a reference to the service held, and with + * the service's state lock held. The state lock will be dropped by waiting + * but reacquired before returning, unless -ENOLINK is returned, in which case + * the service driver has been unbound and the lock cannot be reacquired. + */ +#define __vs_wait_state(_state, _cond, _intr, _timeout, _bh) \ + ({ \ + DEFINE_WAIT(__wait); \ + int __ret; \ + int __jiffies __maybe_unused = (_timeout); \ + struct vs_service_device *__service = (_state)->service;\ + \ + while (1) { \ + prepare_to_wait(&__service->quota_wq, &__wait, \ + _intr ? TASK_INTERRUPTIBLE : \ + TASK_UNINTERRUPTIBLE); \ + \ + if (!VSERVICE_BASE_STATE_IS_RUNNING( \ + (_state)->state.base)) { \ + __ret = -ECANCELED; \ + break; \ + } \ + \ + if (_cond) { \ + __ret = 0; \ + break; \ + } \ + \ + if (_intr && signal_pending(current)) { \ + __ret = -ERESTARTSYS; \ + break; \ + } \ + \ + vs_state_unlock##_bh(_state); \ + \ + if (_timeout >= 0) { \ + __jiffies = schedule_timeout(__jiffies);\ + if (!__jiffies) { \ + __ret = -ETIMEDOUT; \ + break; \ + } \ + } else { \ + schedule(); \ + } \ + \ + if (!vs_state_lock_safe##_bh(_state)) { \ + __ret = -ENOLINK; \ + break; \ + } \ + } \ + \ + finish_wait(&__service->quota_wq, &__wait); \ + __ret; \ + }) + +/* Specialisations of __vs_wait_state for common uses. */ +#define vs_wait_state(_state, _cond) \ + __vs_wait_state(_state, _cond, true, -1,) +#define vs_wait_state_timeout(_state, _cond, _timeout) \ + __vs_wait_state(_state, _cond, true, _timeout,) +#define vs_wait_state_nointr(_state, _cond) \ + __vs_wait_state(_state, _cond, false, -1,) +#define vs_wait_state_nointr_timeout(_state, _cond, _timeout) \ + __vs_wait_state(_state, _cond, false, _timeout,) +#define vs_wait_state_bh(_state, _cond) \ + __vs_wait_state(_state, _cond, true, -1, _bh) +#define vs_wait_state_timeout_bh(_state, _cond, _timeout) \ + __vs_wait_state(_state, _cond, true, _timeout, _bh) +#define vs_wait_state_nointr_bh(_state, _cond) \ + __vs_wait_state(_state, _cond, false, -1, _bh) +#define vs_wait_state_nointr_timeout_bh(_state, _cond, _timeout) \ + __vs_wait_state(_state, _cond, false, _timeout, _bh) + +/** + * __vs_wait_alloc - block until quota is available, then allocate a buffer. + * + * @_state: The protocol state to allocate a message for. + * @_alloc_func: The message buffer allocation function to run. This is the + * full function invocation, not a pointer to the function. + * @_cond: Additional condition which must remain true, or else the wait + * will fail with -ECANCELED. This is typically used to check the + * service's protocol state. Note that this condition will only + * be checked after sleeping; it is assumed to be true when the + * macro is first called. + * @_unlock: If true, drop the service state lock before sleeping. The wait + * may then fail with -ENOLINK if the driver is detached from the + * service, in which case the lock is dropped. + * @_intr: If true, perform an interruptible wait; the wait may then fail + * with -ERESTARTSYS. + * @_timeout: A timeout in jiffies, or negative for no timeout. If the + * timeout expires, the wait will fail with -ETIMEDOUT. + * @_bh: The token _bh if this service uses tx_atomic (sends from a + * non-framework tasklet); otherwise nothing. + * + * Return: Return a pointer to a message buffer on successful allocation, + * or an error code in ERR_PTR form. + * + * This macro calls a specified message allocation function, and blocks + * if it returns -ENOBUFS, waiting until quota is available on the service + * before retrying. It aborts the wait if the service resets, or if the + * optionally specified condition becomes false. Note that a reset followed + * quickly by an activate might not trigger a failure; if that is significant + * for your driver, use the optional condition to detect it. + * + * This macro must be called with a reference to the service held, and with + * the service's state lock held. The reference and state lock will still be + * held on return, unless -ENOLINK is returned, in which case the lock has been + * dropped and cannot be reacquired. + * + * This is always an exclusive wait. It is safe to call without separately + * waking the waitqueue afterwards; if the allocator function fails for any + * reason other than quota exhaustion then another waiter will be woken. + * + * Be wary of potential deadlocks when using this macro on the service + * workqueue. If both ends block their service workqueues waiting for quota, + * then no progress can be made. It is usually only correct to block the + * service workqueue on the server side. + */ +#define __vs_wait_alloc(_state, _alloc_func, _cond, _unlock, _intr, \ + _timeout, _bh) \ + ({ \ + DEFINE_WAIT(__wait); \ + struct vs_mbuf *__mbuf = NULL; \ + int __jiffies __maybe_unused = (_timeout); \ + struct vs_service_device *__service = (_state)->service;\ + \ + while (!vs_service_send_mbufs_available(__service)) { \ + if (_intr && signal_pending(current)) { \ + __mbuf = ERR_PTR(-ERESTARTSYS); \ + break; \ + } \ + \ + prepare_to_wait_exclusive( \ + &__service->quota_wq, &__wait, \ + _intr ? TASK_INTERRUPTIBLE : \ + TASK_UNINTERRUPTIBLE); \ + \ + if (_unlock) \ + vs_state_unlock##_bh(_state); \ + \ + if (_timeout >= 0) { \ + __jiffies = schedule_timeout(__jiffies);\ + if (!__jiffies) { \ + __mbuf = ERR_PTR(-ETIMEDOUT); \ + break; \ + } \ + } else { \ + schedule(); \ + } \ + \ + if (_unlock && !vs_state_lock_safe##_bh( \ + _state)) { \ + __mbuf = ERR_PTR(-ENOLINK); \ + break; \ + } \ + \ + if (!VSERVICE_BASE_STATE_IS_RUNNING( \ + (_state)->state.base) || \ + !(_cond)) { \ + __mbuf = ERR_PTR(-ECANCELED); \ + break; \ + } \ + } \ + finish_wait(&__service->quota_wq, &__wait); \ + \ + if (__mbuf == NULL) \ + __mbuf = (_alloc_func); \ + if (IS_ERR(__mbuf) && (PTR_ERR(__mbuf) != -ENOBUFS)) \ + wake_up(&__service->quota_wq); \ + __mbuf; \ + }) + +/* Specialisations of __vs_wait_alloc for common uses. */ +#define vs_wait_alloc(_state, _cond, _alloc_func) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1,) +#define vs_wait_alloc_timeout(_state, _cond, _alloc_func, _timeout) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, true, _timeout,) +#define vs_wait_alloc_nointr(_state, _cond, _alloc_func) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1,) +#define vs_wait_alloc_nointr_timeout(_state, _cond, _alloc_func, _timeout) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, false, _timeout,) +#define vs_wait_alloc_bh(_state, _cond, _alloc_func) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1, _bh) +#define vs_wait_alloc_timeout_bh(_state, _cond, _alloc_func, _timeout) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, true, _timeout, _bh) +#define vs_wait_alloc_nointr_bh(_state, _cond, _alloc_func) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1, _bh) +#define vs_wait_alloc_nointr_timeout_bh(_state, _cond, _alloc_func, _timeout) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, false, _timeout, _bh) +#define vs_wait_alloc_locked(_state, _alloc_func) \ + __vs_wait_alloc(_state, _alloc_func, true, false, true, -1,) + +/* Legacy wait macros, to be removed and replaced with those above. */ +/* FIXME: Redmine ticket #229 - philip. */ +#define vs_service_waiting_alloc(_state, _alloc_func) \ + __vs_wait_alloc(_state, _alloc_func, true, false, true, -1,) +#define vs_service_waiting_alloc_cond_locked(_state, _alloc_func, _cond) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1,) +#define vs_service_waiting_alloc_cond_locked_nointr(_state, _alloc_func, _cond) \ + __vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1,) + +/** + * __vs_wait_send - block until quota is available, then send a message. + * + * @_state: The protocol state to send a message for. + * @_cond: Additional condition which must remain true, or else the wait + * will fail with -ECANCELED. This is typically used to check the + * service's protocol state. Note that this condition will only + * be checked after sleeping; it is assumed to be true when the + * macro is first called. + * @_send_func: The message send function to run. This is the full function + * invocation, not a pointer to the function. + * @_unlock: If true, drop the service state lock before sleeping. The wait + * may then fail with -ENOLINK if the driver is detached from the + * service, in which case the lock is dropped. + * @_check_running: If true, the wait will return -ECANCELED if the service's + * base state is not active, or ceases to be active. + * @_intr: If true, perform an interruptible wait; the wait may then fail + * with -ERESTARTSYS. + * @_timeout: A timeout in jiffies, or negative for no timeout. If the + * timeout expires, the wait will fail with -ETIMEDOUT. + * @_bh: The token _bh if this service uses tx_atomic (sends from a + * non-framework tasklet); otherwise nothing. + * + * Return: If the send succeeds, then 0 is returned; otherwise an error + * code may be returned as described above. + * + * This macro calls a specified message send function, and blocks if it + * returns -ENOBUFS, waiting until quota is available on the service before + * retrying. It aborts the wait if it finds the service in reset, or if the + * optionally specified condition becomes false. Note that a reset followed + * quickly by an activate might not trigger a failure; if that is significant + * for your driver, use the optional condition to detect it. + * + * This macro must be called with a reference to the service held, and with + * the service's state lock held. The reference and state lock will still be + * held on return, unless -ENOLINK is returned, in which case the lock has been + * dropped and cannot be reacquired. + * + * This is always an exclusive wait. It is safe to call without separately + * waking the waitqueue afterwards; if the allocator function fails for any + * reason other than quota exhaustion then another waiter will be woken. + * + * Be wary of potential deadlocks when calling this function on the service + * workqueue. If both ends block their service workqueues waiting for quota, + * then no progress can be made. It is usually only correct to block the + * service workqueue on the server side. + */ +#define __vs_wait_send(_state, _cond, _send_func, _unlock, \ + _check_running, _intr, _timeout, _bh) \ + ({ \ + DEFINE_WAIT(__wait); \ + int __ret = 0; \ + int __jiffies __maybe_unused = (_timeout); \ + struct vs_service_device *__service = (_state)->service;\ + \ + while (!vs_service_send_mbufs_available(__service)) { \ + if (_intr && signal_pending(current)) { \ + __ret = -ERESTARTSYS; \ + break; \ + } \ + \ + prepare_to_wait_exclusive( \ + &__service->quota_wq, &__wait, \ + _intr ? TASK_INTERRUPTIBLE : \ + TASK_UNINTERRUPTIBLE); \ + \ + if (_unlock) \ + vs_state_unlock##_bh(_state); \ + \ + if (_timeout >= 0) { \ + __jiffies = schedule_timeout(__jiffies);\ + if (!__jiffies) { \ + __ret = -ETIMEDOUT; \ + break; \ + } \ + } else { \ + schedule(); \ + } \ + \ + if (_unlock && !vs_state_lock_safe##_bh( \ + _state)) { \ + __ret = -ENOLINK; \ + break; \ + } \ + \ + if ((_check_running && \ + !VSERVICE_BASE_STATE_IS_RUNNING(\ + (_state)->state.base)) || \ + !(_cond)) { \ + __ret = -ECANCELED; \ + break; \ + } \ + } \ + finish_wait(&__service->quota_wq, &__wait); \ + \ + if (!__ret) \ + __ret = (_send_func); \ + if ((__ret < 0) && (__ret != -ENOBUFS)) \ + wake_up(&__service->quota_wq); \ + __ret; \ + }) + +/* Specialisations of __vs_wait_send for common uses. */ +#define vs_wait_send(_state, _cond, _send_func) \ + __vs_wait_send(_state, _cond, _send_func, true, true, true, -1,) +#define vs_wait_send_timeout(_state, _cond, _send_func, _timeout) \ + __vs_wait_send(_state, _cond, _send_func, true, true, true, _timeout,) +#define vs_wait_send_nointr(_state, _cond, _send_func) \ + __vs_wait_send(_state, _cond, _send_func, true, true, false, -1,) +#define vs_wait_send_nointr_timeout(_state, _cond, _send_func, _timeout) \ + __vs_wait_send(_state, _cond, _send_func, true, true, false, _timeout,) +#define vs_wait_send_bh(_state, _cond, _send_func) \ + __vs_wait_send(_state, _cond, _send_func, true, true, true, -1, _bh) +#define vs_wait_send_timeout_bh(_state, _cond, _send_func, _timeout) \ + __vs_wait_send(_state, _cond, _send_func, true, true, true, \ + _timeout, _bh) +#define vs_wait_send_nointr_bh(_state, _cond, _send_func) \ + __vs_wait_send(_state, _cond, _send_func, true, true, false, -1, _bh) +#define vs_wait_send_nointr_timeout_bh(_state, _cond, _send_func, _timeout) \ + __vs_wait_send(_state, _cond, _send_func, true, true, false, \ + _timeout, _bh) +#define vs_wait_send_locked(_state, _send_func) \ + __vs_wait_send(_state, true, _send_func, false, true, true, -1,) +#define vs_wait_send_locked_nocheck(_state, _send_func) \ + __vs_wait_send(_state, true, _send_func, false, false, true, -1,) + +/* Legacy wait macros, to be removed and replaced with those above. */ +/* FIXME: Redmine ticket #229 - philip. */ +#define vs_service_waiting_send(_state, _send_func) \ + __vs_wait_send(_state, true, _send_func, true, true, true, -1,) +#define vs_service_waiting_send_nointr(_state, _send_func) \ + __vs_wait_send(_state, true, _send_func, true, true, false, -1,) +#define vs_service_waiting_send_cond(_state, _cond, _send_func) \ + __vs_wait_send(_state, _cond, _send_func, true, true, true, -1,) +#define vs_service_waiting_send_cond_nointr(_state, _cond, _send_func) \ + __vs_wait_send(_state, _cond, _send_func, true, true, false, -1,) +#define vs_service_waiting_send_nocheck(_state, _send_func) \ + __vs_wait_send(_state, true, _send_func, true, false, true, -1,) + +#endif /* _VSERVICE_SERVICE_WAIT_H */