/* * 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", msg->service->id, err); vs_service_reset_nosync(client->service); break; } /* * The message sent successfully - remove it from the queue. * The corresponding vs_get_service() was done when the pending * message was enqueued. */ vs_put_service(msg->service); list_del(&msg->list); kfree(msg); } mutex_unlock(&client->message_queue_lock); vs_service_state_unlock(client->service); } static int vs_client_core_handle_server_ready(struct vs_client_core_state *state, u32 service_id, u32 in_quota, u32 out_quota, u32 in_bit_offset, u32 in_num_bits, u32 out_bit_offset, u32 out_num_bits) { struct core_client *client = to_core_client(state); struct vs_session_device *session; struct vs_service_device *service; int ret; if (service_id == 0) return -EPROTO; if (!in_quota || !out_quota) return -EINVAL; session = vs_service_get_session(client->service); service = vs_session_get_service(session, service_id); if (!service) return -EINVAL; service->send_quota = in_quota; service->recv_quota = out_quota; service->notify_send_offset = in_bit_offset; service->notify_send_bits = in_num_bits; service->notify_recv_offset = out_bit_offset; service->notify_recv_bits = out_num_bits; ret = vs_service_enable(service); vs_put_service(service); return ret; } static int vs_client_core_handle_service_reset(struct vs_client_core_state *state, u32 service_id) { struct core_client *client = to_core_client(state); struct vs_session_device *session; if (service_id == 0) return -EPROTO; session = vs_service_get_session(client->service); return vs_service_handle_reset(session, service_id, true); } static void vs_core_client_start(struct vs_client_core_state *state) { struct core_client *client = to_core_client(state); struct vs_session_device *session = vs_service_get_session(client->service); /* FIXME - start callback should return int */ vs_dev_debug(VS_DEBUG_CLIENT_CORE, session, &client->service->dev, "Core client start\n"); } static void vs_core_client_reset(struct vs_client_core_state *state) { struct core_client *client = to_core_client(state); struct vs_session_device *session = vs_service_get_session(client->service); struct pending_reset *msg; /* Flush the pending resets - we're about to delete everything */ while (!list_empty(&client->message_queue)) { msg = list_first_entry(&client->message_queue, struct pending_reset, list); vs_put_service(msg->service); list_del(&msg->list); kfree(msg); } vs_session_delete_noncore(session); /* Return to the initial quotas, until the next startup message */ client->service->send_quota = 0; client->service->recv_quota = 1; } static int vs_core_client_startup(struct vs_client_core_state *state, u32 core_in_quota, u32 core_out_quota) { struct core_client *client = to_core_client(state); struct vs_service_device *service = state->service; struct vs_session_device *session = vs_service_get_session(service); int ret; if (!core_in_quota || !core_out_quota) return -EINVAL; /* * Update the service struct with our real quotas and tell the * transport about the change */ service->send_quota = core_in_quota; service->recv_quota = core_out_quota; ret = session->transport->vt->service_start(session->transport, service); if (ret < 0) return ret; WARN_ON(!list_empty(&client->message_queue)); return vs_client_core_core_req_connect(state, GFP_KERNEL); } static struct vs_client_core_state * vs_core_client_alloc(struct vs_service_device *service) { struct core_client *client; int err; client = kzalloc(sizeof(*client), GFP_KERNEL); if (!client) goto fail; client->service = service; INIT_LIST_HEAD(&client->message_queue); INIT_WORK(&client->message_queue_work, message_queue_work); mutex_init(&client->message_queue_lock); err = sysfs_create_group(&service->dev.kobj, &client_core_attr_group); if (err) goto fail_free_client; /* * Default transport resources for the core service client. The * server will inform us of the real quotas in the startup message. * Note that it is important that the quotas never decrease, so these * numbers are as small as possible. */ service->send_quota = 0; service->recv_quota = 1; service->notify_send_bits = 0; service->notify_send_offset = 0; service->notify_recv_bits = 0; service->notify_recv_offset = 0; return &client->state; fail_free_client: kfree(client); fail: return NULL; } static void vs_core_client_release(struct vs_client_core_state *state) { struct core_client *client = to_core_client(state); sysfs_remove_group(&client->service->dev.kobj, &client_core_attr_group); kfree(client); } static struct vs_client_core vs_core_client_driver = { .alloc = vs_core_client_alloc, .release = vs_core_client_release, .start = vs_core_client_start, .reset = vs_core_client_reset, .tx_ready = vs_core_client_tx_ready, .core = { .nack_connect = vs_client_core_fatal_error, /* FIXME: Jira ticket SDK-3074 - ryanm. */ .ack_disconnect = vs_client_core_fatal_error, .nack_disconnect = vs_client_core_fatal_error, .msg_service_created = vs_client_core_handle_service_created, .msg_service_removed = vs_client_core_handle_service_removed, .msg_startup = vs_core_client_startup, /* FIXME: Jira ticket SDK-3074 - philipd. */ .msg_shutdown = vs_client_core_fatal_error, .msg_server_ready = vs_client_core_handle_server_ready, .msg_service_reset = vs_client_core_handle_service_reset, }, }; /* * Client bus driver */ static int vs_client_bus_match(struct device *dev, struct device_driver *driver) { struct vs_service_device *service = to_vs_service_device(dev); struct vs_service_driver *vsdrv = to_vs_service_driver(driver); /* Don't match anything to the devio driver; it's bound manually */ if (!vsdrv->protocol) return 0; WARN_ON_ONCE(service->is_server || vsdrv->is_server); /* Match if the protocol strings are the same */ if (strcmp(service->protocol, vsdrv->protocol) == 0) return 1; return 0; } static ssize_t is_server_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vs_service_device *service = to_vs_service_device(dev); return scnprintf(buf, PAGE_SIZE, "%d\n", service->is_server); } static DEVICE_ATTR_RO(is_server); static ssize_t id_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vs_service_device *service = to_vs_service_device(dev); return scnprintf(buf, PAGE_SIZE, "%d\n", service->id); } static DEVICE_ATTR_RO(id); static ssize_t dev_protocol_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vs_service_device *service = to_vs_service_device(dev); return scnprintf(buf, PAGE_SIZE, "%s\n", service->protocol ?: ""); } static DEVICE_ATTR(protocol, 0444, dev_protocol_show, NULL); static ssize_t service_name_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vs_service_device *service = to_vs_service_device(dev); return scnprintf(buf, PAGE_SIZE, "%s\n", service->name); } static DEVICE_ATTR_RO(service_name); static ssize_t quota_in_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vs_service_device *service = to_vs_service_device(dev); return scnprintf(buf, PAGE_SIZE, "%d\n", service->send_quota); } static DEVICE_ATTR_RO(quota_in); static ssize_t quota_out_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vs_service_device *service = to_vs_service_device(dev); return scnprintf(buf, PAGE_SIZE, "%d\n", service->recv_quota); } static DEVICE_ATTR_RO(quota_out); static struct attribute *vs_client_dev_attrs[] = { &dev_attr_id.attr, &dev_attr_is_server.attr, &dev_attr_protocol.attr, &dev_attr_service_name.attr, &dev_attr_quota_in.attr, &dev_attr_quota_out.attr, NULL, }; ATTRIBUTE_GROUPS(vs_client_dev); static ssize_t protocol_show(struct device_driver *drv, char *buf) { struct vs_service_driver *driver = to_vs_service_driver(drv); return scnprintf(buf, PAGE_SIZE, "%s\n", driver->protocol); } static DRIVER_ATTR_RO(protocol); static struct attribute *vs_client_drv_attrs[] = { &driver_attr_protocol.attr, NULL, }; ATTRIBUTE_GROUPS(vs_client_drv); struct bus_type vs_client_bus_type = { .name = "vservices-client", .dev_groups = vs_client_dev_groups, .drv_groups = vs_client_drv_groups, .match = vs_client_bus_match, .probe = vs_service_bus_probe, .remove = vs_service_bus_remove, .uevent = vs_service_bus_uevent, }; EXPORT_SYMBOL(vs_client_bus_type); /* * Client session driver */ static int vs_client_session_probe(struct device *dev) { struct vs_session_device *session = to_vs_session_device(dev); struct vs_service_device *service; char *protocol, *name; int ret = 0; if (session->is_server) { ret = -ENODEV; goto fail; } /* create a service for the core protocol client */ protocol = kstrdup(VSERVICE_CORE_PROTOCOL_NAME, GFP_KERNEL); if (!protocol) { ret = -ENOMEM; goto fail; } name = kstrdup("core", GFP_KERNEL); if (!name) { ret = -ENOMEM; goto fail_free_protocol; } service = vs_service_register(session, NULL, 0, protocol, name, NULL); if (IS_ERR(service)) { ret = PTR_ERR(service); goto fail_free_name; } fail_free_name: kfree(name); fail_free_protocol: kfree(protocol); fail: return ret; } static int vs_client_session_send_service_reset(struct vs_session_device *session, struct vs_service_device *service) { if (WARN_ON(service->id == 0)) return -EINVAL; return vs_client_core_queue_service_reset(session, service); } static struct vs_session_driver vs_client_session_driver = { .driver = { .name = "vservices-client-session", .owner = THIS_MODULE, .bus = &vs_session_bus_type, .probe = vs_client_session_probe, .suppress_bind_attrs = true, }, .is_server = false, .service_bus = &vs_client_bus_type, .service_local_reset = vs_client_session_send_service_reset, }; static int __init vs_core_client_init(void) { int ret; ret = bus_register(&vs_client_bus_type); if (ret) goto fail_bus_register; #ifdef CONFIG_VSERVICES_CHAR_DEV vs_devio_client_driver.driver.bus = &vs_client_bus_type; vs_devio_client_driver.driver.owner = THIS_MODULE; ret = driver_register(&vs_devio_client_driver.driver); if (ret) goto fail_devio_register; #endif ret = driver_register(&vs_client_session_driver.driver); if (ret) goto fail_driver_register; ret = vservice_core_client_register(&vs_core_client_driver, "vs_core_client"); if (ret) goto fail_core_register; vservices_client_root = kobject_create_and_add("client-sessions", vservices_root); if (!vservices_client_root) { ret = -ENOMEM; goto fail_create_root; } return 0; fail_create_root: vservice_core_client_unregister(&vs_core_client_driver); fail_core_register: driver_unregister(&vs_client_session_driver.driver); fail_driver_register: #ifdef CONFIG_VSERVICES_CHAR_DEV driver_unregister(&vs_devio_client_driver.driver); vs_devio_client_driver.driver.bus = NULL; vs_devio_client_driver.driver.owner = NULL; fail_devio_register: #endif bus_unregister(&vs_client_bus_type); fail_bus_register: return ret; } static void __exit vs_core_client_exit(void) { kobject_put(vservices_client_root); vservice_core_client_unregister(&vs_core_client_driver); driver_unregister(&vs_client_session_driver.driver); #ifdef CONFIG_VSERVICES_CHAR_DEV driver_unregister(&vs_devio_client_driver.driver); vs_devio_client_driver.driver.bus = NULL; vs_devio_client_driver.driver.owner = NULL; #endif bus_unregister(&vs_client_bus_type); } subsys_initcall(vs_core_client_init); module_exit(vs_core_client_exit); MODULE_DESCRIPTION("OKL4 Virtual Services Core Client Driver"); MODULE_AUTHOR("Open Kernel Labs, Inc");