/* * 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 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");