Files
kernel_xiaomi_sm8250/drivers/vservices/core_server.c
Murali Nalajala abf9e66197 vservices: core: Port core vservices drivers to linux 4.14
Vservices core drivers are broken on 4.14 kernel due to core
framework changes.This commit updates the virtual services
core to build and run on linux 4.14.

Change-Id: I53360a8b8431d58c4569d4d4c3b86f3c820b6faf
Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
2019-01-09 11:00:12 -08:00

1650 lines
44 KiB
C

/*
* 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 <linux/kernel.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <vservices/types.h>
#include <vservices/transport.h>
#include <vservices/session.h>
#include <vservices/buffer.h>
#include <vservices/service.h>
#include <vservices/protocol/core/types.h>
#include <vservices/protocol/core/common.h>
#include <vservices/protocol/core/server.h>
#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 DEVICE_ATTR_RO(is_server);
static ssize_t id_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct vs_service_device *service = to_vs_service_device(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", service->id);
}
static DEVICE_ATTR_RO(id);
static ssize_t dev_protocol_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct vs_service_device *service = to_vs_service_device(dev);
return scnprintf(buf, PAGE_SIZE, "%s\n", service->protocol ?: "");
}
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 DEVICE_ATTR(protocol, 0644,
dev_protocol_show, dev_protocol_store);
static ssize_t service_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct vs_service_device *service = to_vs_service_device(dev);
return scnprintf(buf, PAGE_SIZE, "%s\n", service->name);
}
static DEVICE_ATTR_RO(service_name);
static ssize_t quota_in_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 DEVICE_ATTR_RW(quota_in);
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 DEVICE_ATTR_RW(quota_out);
static struct attribute *vs_server_dev_attrs[] = {
&dev_attr_id.attr,
&dev_attr_is_server.attr,
&dev_attr_protocol.attr,
&dev_attr_service_name.attr,
&dev_attr_quota_in.attr,
&dev_attr_quota_out.attr,
NULL,
};
ATTRIBUTE_GROUPS(vs_server_dev);
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);
}
static DRIVER_ATTR_RO(protocol);
static struct attribute *vs_server_drv_attrs[] = {
&driver_attr_protocol.attr,
NULL,
};
ATTRIBUTE_GROUPS(vs_server_drv);
struct bus_type vs_server_bus_type = {
.name = "vservices-server",
.dev_groups = vs_server_dev_groups,
.drv_groups = vs_server_drv_groups,
.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");